diff --git a/.github/workflows/ami-build.yml b/.github/workflows/ami-build.yml index 0bc6a4569..f5352369f 100644 --- a/.github/workflows/ami-build.yml +++ b/.github/workflows/ami-build.yml @@ -184,22 +184,22 @@ jobs: --output table fi - - name: Make AMI public (prod only) + - name: Tag AMI for Marketplace (prod only) if: env.AMI_ID && env.STAGE == 'prod' run: | - echo "🌍 Making AMI public for production release..." - aws ec2 modify-image-attribute \ - --image-id "$AMI_ID" \ - --launch-permission "Add=[{Group=all}]" \ + echo "🏷️ Tagging AMI for AWS Marketplace distribution..." + aws ec2 create-tags \ + --resources "$AMI_ID" \ + --tags \ + Key=marketplace,Value=ready \ + Key=version,Value=${{ github.ref_name || 'latest' }} \ + Key=stage,Value=${{ env.STAGE }} \ --region "${{ env.AWS_REGION }}" - echo "✅ AMI is now publicly available in Community AMIs" - - # Verify public status - aws ec2 describe-image-attribute \ - --image-id "$AMI_ID" \ - --attribute launchPermission \ - --region "${{ env.AWS_REGION }}" + echo "✅ AMI is ready for Marketplace submission" + echo "📝 AMI is private and unencrypted (Marketplace requirement)" + echo "🔗 Next: Submit to AWS Marketplace Seller Console" + echo " https://aws.amazon.com/marketplace/management/products" - name: Update GitHub release with AMI details if: github.event_name == 'release' && env.AMI_ID @@ -218,7 +218,7 @@ jobs: **Stage:** \`${stage}\` **AMI ID:** \`${amiId}\` **Region:** \`${region}\` - **Availability:** ${stage === 'prod' ? '🌍 Public (Community AMIs)' : '🔒 Private'} + **Availability:** ${stage === 'prod' ? '🏪 Ready for AWS Marketplace' : '🔒 Private (Dev)'} **Launch URL:** \\ https://console.aws.amazon.com/ec2/home?region=${region}#LaunchInstances:ami=${amiId} diff --git a/deployments/infra/stacks/ami_pipeline_stack.go b/deployments/infra/stacks/ami_pipeline_stack.go index ea1a21114..c54908f65 100644 --- a/deployments/infra/stacks/ami_pipeline_stack.go +++ b/deployments/infra/stacks/ami_pipeline_stack.go @@ -24,14 +24,15 @@ type AmiPipelineStackProps struct { } type AmiPipelineStackExports struct { - PipelineArn string - DockerComponentArn string - ConfigComponentArn string - RecipeArn string - InfraConfigArn string - DistributionArn string - S3BucketName string - InstanceProfileArn string + PipelineArn string + DockerComponentArn string + ConfigComponentArn string + WelcomeComponentArn string + RecipeArn string + InfraConfigArn string + DistributionArn string + S3BucketName string + InstanceProfileArn string } func AmiPipelineStack(scope constructs.Construct, id string, props *AmiPipelineStackProps) (awscdk.Stack, AmiPipelineStackExports) { @@ -173,7 +174,7 @@ phases: `), }) - // Component for TRUF.NETWORK configuration + // Component 1: TRUF.NETWORK configuration and scripts configComponent := awsimagebuilder.NewCfnComponent(stack, jsii.String("TNConfigComponent"), &awsimagebuilder.CfnComponentProps{ Name: jsii.String(nameWithPrefix("tn-config-setup-" + string(stage))), Platform: jsii.String("Linux"), @@ -248,6 +249,7 @@ phases: # Default values PRIVATE_KEY="" ENABLE_MCP=false + NETWORK="" # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -260,6 +262,10 @@ phases: ENABLE_MCP=true shift ;; + --network) + NETWORK="$2" + shift 2 + ;; *) echo "Unknown option $1" exit 1 @@ -276,16 +282,23 @@ phases: echo "Configuring TRUF.NETWORK node..." fi - echo "Network: mainnet (tn-v2.1)" + # Determine network type and chain ID + if [ -n "$NETWORK" ]; then + CHAIN_ID="$NETWORK" + NETWORK_TYPE="custom" + echo "Network: Custom network ($CHAIN_ID)" + else + CHAIN_ID="tn-v2.1" + NETWORK_TYPE="mainnet" + echo "Network: Mainnet (tn-v2.1)" + fi echo "MCP enabled: $ENABLE_MCP" - # Chain ID is always tn-v2.1 regardless of network - CHAIN_ID="tn-v2.1" cd /opt/tn # Handle configuration (new or reconfigure) if [ "$RECONFIGURE" = true ]; then - # Block private key changes during reconfiguration + # Block private key and network changes during reconfiguration if [ -n "$PRIVATE_KEY" ]; then echo "❌ Error: Cannot change private key on existing node!" echo "Private key changes would alter node identity and cause network issues." @@ -297,11 +310,30 @@ phases: exit 1 fi - # Preserve existing private key + if [ -n "$NETWORK" ]; then + echo "❌ Error: Cannot change network on existing node!" + echo "Network changes require a fresh deployment." + echo "" + echo "You can only reconfigure MCP settings:" + echo " sudo tn-node-configure --enable-mcp" + echo " sudo tn-node-configure # (disable MCP)" + exit 1 + fi + + # Preserve existing configuration if grep -q "TN_PRIVATE_KEY=" .env; then EXISTING_KEY=$(grep "TN_PRIVATE_KEY=" .env | cut -d'=' -f2) echo "Preserving existing node identity" fi + if grep -q "CHAIN_ID=" .env; then + EXISTING_CHAIN_ID=$(grep "CHAIN_ID=" .env | cut -d'=' -f2) + CHAIN_ID="$EXISTING_CHAIN_ID" + echo "Preserving existing chain ID: $CHAIN_ID" + fi + if grep -q "NETWORK_TYPE=" .env; then + EXISTING_NETWORK_TYPE=$(grep "NETWORK_TYPE=" .env | cut -d'=' -f2) + NETWORK_TYPE="$EXISTING_NETWORK_TYPE" + fi # Stop service for reconfiguration echo "Stopping existing services..." @@ -312,6 +344,7 @@ phases: # Create/update .env file cat > .env << ENVEOF CHAIN_ID=$CHAIN_ID + NETWORK_TYPE=$NETWORK_TYPE ENVEOF # Handle MCP configuration @@ -383,8 +416,23 @@ phases: inputs: commands: - sudo systemctl daemon-reload - # Note: We do not enable tn-node immediately - we let users configure first +`), + }) + + // Component 2: Welcome messages (split to stay under 16KB limit) + welcomeComponent := awsimagebuilder.NewCfnComponent(stack, jsii.String("TNWelcomeComponent"), &awsimagebuilder.CfnComponentProps{ + Name: jsii.String(nameWithPrefix("tn-welcome-setup-" + string(stage))), + Platform: jsii.String("Linux"), + Version: jsii.String("1.0.0"), + Description: jsii.String("Set up TRUF.NETWORK welcome messages and user guidance"), + Data: jsii.String(` +name: TNWelcomeComponent +description: Set up TRUF.NETWORK welcome messages and user guidance +schemaVersion: 1.0 +phases: + - name: build + steps: - name: CreateWelcomeMessage action: ExecuteBash inputs: @@ -468,7 +516,7 @@ phases: // Add explicit dependency infraConfig.AddDependency(instanceProfile) - // Image recipe that combines both components + // Image recipe that combines all components imageRecipe := awsimagebuilder.NewCfnImageRecipe(stack, jsii.String("TNAmiRecipe"), &awsimagebuilder.CfnImageRecipeProps{ Name: jsii.String(nameWithPrefix("tn-ami-recipe-" + string(stage))), Version: jsii.String("1.0.0"), @@ -481,6 +529,9 @@ phases: { ComponentArn: configComponent.AttrArn(), }, + { + ComponentArn: welcomeComponent.AttrArn(), + }, }, BlockDeviceMappings: &[]*awsimagebuilder.CfnImageRecipe_InstanceBlockDeviceMappingProperty{ { @@ -488,7 +539,7 @@ phases: Ebs: &awsimagebuilder.CfnImageRecipe_EbsInstanceBlockDeviceSpecificationProperty{ VolumeSize: jsii.Number(20), // 20GB root volume VolumeType: jsii.String("gp3"), - Encrypted: jsii.Bool(true), + Encrypted: jsii.Bool(false), // AWS Marketplace prohibits encrypted AMIs DeleteOnTermination: jsii.Bool(true), }, }, @@ -539,14 +590,15 @@ phases: }) exports := AmiPipelineStackExports{ - PipelineArn: *imagePipeline.AttrArn(), - DockerComponentArn: *dockerComponent.AttrArn(), - ConfigComponentArn: *configComponent.AttrArn(), - RecipeArn: *imageRecipe.AttrArn(), - InfraConfigArn: *infraConfig.AttrArn(), - DistributionArn: *distributionConfig.AttrArn(), - S3BucketName: *artifactsBucket.BucketName(), - InstanceProfileArn: *instanceProfile.AttrArn(), + PipelineArn: *imagePipeline.AttrArn(), + DockerComponentArn: *dockerComponent.AttrArn(), + ConfigComponentArn: *configComponent.AttrArn(), + WelcomeComponentArn: *welcomeComponent.AttrArn(), + RecipeArn: *imageRecipe.AttrArn(), + InfraConfigArn: *infraConfig.AttrArn(), + DistributionArn: *distributionConfig.AttrArn(), + S3BucketName: *artifactsBucket.BucketName(), + InstanceProfileArn: *instanceProfile.AttrArn(), } return stack, exports diff --git a/deployments/infra/stacks/docker-compose.template.yml b/deployments/infra/stacks/docker-compose.template.yml index 9555b09b9..be6c4d0ee 100644 --- a/deployments/infra/stacks/docker-compose.template.yml +++ b/deployments/infra/stacks/docker-compose.template.yml @@ -56,8 +56,19 @@ services: [ -n \"$$PUBLIC_IP\" ] && EXTERNAL_FLAG=\"--p2p.external-address $$PUBLIC_IP:26656\" || EXTERNAL_FLAG=\"\" echo \"Detected public IP: $$PUBLIC_IP\" - # Generate full configuration with kwild setup init - /app/kwild setup init --genesis /opt/tn/configs/network/v2/genesis.json --root /root/.kwild-new --p2p.bootnodes \"4e0b5c952be7f26698dc1898ff3696ac30e990f25891aeaf88b0285eab4663e1#ed25519@node-1.mainnet.truf.network:26656,0c830b69790eaa09315826403c2008edc65b5c7132be9d4b7b4da825c2a166ae#ed25519@node-2.mainnet.truf.network:26656\" --state-sync.enable --state-sync.trusted-providers \"4e0b5c952be7f26698dc1898ff3696ac30e990f25891aeaf88b0285eab4663e1#ed25519@node-1.mainnet.truf.network:26656\" --rpc.private --db.host tn-postgres $$EXTERNAL_FLAG + # Determine network type and generate appropriate configuration + NETWORK_TYPE=\"$${NETWORK_TYPE:-mainnet}\" + + if [ \"$$NETWORK_TYPE\" = \"custom\" ]; then + echo 'Creating new custom network with chain ID: '\"$$CHAIN_ID\" + # Create new network without genesis file + /app/kwild setup init --root /root/.kwild-new --chain-id \"$$CHAIN_ID\" --rpc.private --db.host tn-postgres $$EXTERNAL_FLAG + else + echo 'Joining existing mainnet (tn-v2.1)...' + # Join existing network using genesis file + /app/kwild setup init --genesis /opt/tn/configs/network/v2/genesis.json --root /root/.kwild-new --p2p.bootnodes \"4e0b5c952be7f26698dc1898ff3696ac30e990f25891aeaf88b0285eab4663e1#ed25519@node-1.mainnet.truf.network:26656,0c830b69790eaa09315826403c2008edc65b5c7132be9d4b7b4da825c2a166ae#ed25519@node-2.mainnet.truf.network:26656\" --state-sync.enable --state-sync.trusted-providers \"4e0b5c952be7f26698dc1898ff3696ac30e990f25891aeaf88b0285eab4663e1#ed25519@node-1.mainnet.truf.network:26656\" --rpc.private --db.host tn-postgres $$EXTERNAL_FLAG + fi + mkdir -p /root/.kwild cp /root/.kwild-new/* /root/.kwild/ rm -rf /root/.kwild-new diff --git a/scripts/test-ami.sh b/scripts/test-ami.sh index d31794147..6a25050a5 100755 --- a/scripts/test-ami.sh +++ b/scripts/test-ami.sh @@ -107,6 +107,12 @@ echo "Using shared Docker Compose configuration..." # Use shared Docker Compose configuration (single source of truth) cp deployments/infra/stacks/docker-compose.template.yml /tmp/test-docker-compose.yml +# Create dummy .env file for validation (docker-compose expects it) +cat > /tmp/.env << 'EOF' +CHAIN_ID=tn-v2.1 +NETWORK_TYPE=mainnet +EOF + if eval "$COMPOSE -f /tmp/test-docker-compose.yml config" > /dev/null 2>&1; then echo -e "${GREEN}✅ Docker Compose configuration valid${NC}" TESTS_PASSED=$((TESTS_PASSED + 1)) @@ -114,7 +120,7 @@ else echo -e "${RED}❌ Docker Compose configuration invalid${NC}" TESTS_FAILED=$((TESTS_FAILED + 1)) fi -rm -f /tmp/test-docker-compose.yml +rm -f /tmp/test-docker-compose.yml /tmp/.env # Test 4: Shell Script Syntax echo "4. Testing shell script syntax..." @@ -396,6 +402,12 @@ echo "Starting PostgreSQL container to test database connectivity..." # Use shared Docker Compose configuration for PostgreSQL test cp deployments/infra/stacks/docker-compose.template.yml /tmp/tn-test-compose.yml +# Create .env file in same directory (docker-compose expects .env relative to compose file) +cat > /tmp/.env << 'EOF' +CHAIN_ID=tn-v2.1 +NETWORK_TYPE=mainnet +EOF + echo "Starting PostgreSQL container..." if eval "$COMPOSE -f /tmp/tn-test-compose.yml up -d tn-postgres"; then echo "Waiting for PostgreSQL to be ready..." @@ -428,8 +440,8 @@ else fi echo "Cleaning up test containers..." -eval "$COMPOSE -f /tmp/tn-test-compose.yml down -v" -rm -f /tmp/tn-test-compose.yml +eval "$COMPOSE -f /tmp/tn-test-compose.yml down -v" 2>/dev/null || true +rm -f /tmp/tn-test-compose.yml /tmp/.env # Test 10: Update Script Workflow echo "10. Testing update script workflow..."