Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/ami-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Comment on lines +187 to 203
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Fix job condition and harden tagging.

  • if: env.AMI_ID && env.STAGE == 'prod' treats AMI_ID as a string, which isn’t a boolean. Use an explicit non-empty check.
  • Keep the expression for version fallback.
-      - name: Tag AMI for Marketplace (prod only)
-        if: env.AMI_ID && env.STAGE == 'prod'
+      - name: Tag AMI for Marketplace (prod only)
+        if: ${{ env.AMI_ID != '' && env.STAGE == 'prod' }}
         run: |
           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 }}"

Also, keep EBS unencrypted for Marketplace (already handled in the recipe). (docs.aws.amazon.com)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 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: Tag AMI for Marketplace (prod only)
if: ${{ env.AMI_ID != '' && env.STAGE == 'prod' }}
run: |
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 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"
🤖 Prompt for AI Agents
.github/workflows/ami-build.yml lines 187-203: the job uses a truthy test for
AMI_ID which treats the string as a boolean; change the job condition to an
explicit non-empty check (e.g. if: env.AMI_ID != '' && env.STAGE == 'prod') so
the step only runs when AMI_ID is present and stage is prod, preserve the
existing version fallback expression for the version tag (${ { github.ref_name
|| 'latest' } }) exactly as-is, and ensure AMI_ID remains quoted in the aws cli
call (e.g. --resources "$AMI_ID") so tagging is robust; no change needed for EBS
encryption because Marketplace requires unencrypted AMIs.

- name: Update GitHub release with AMI details
if: github.event_name == 'release' && env.AMI_ID
Expand All @@ -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}

Expand Down
102 changes: 77 additions & 25 deletions deployments/infra/stacks/ami_pipeline_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -248,6 +249,7 @@ phases:
# Default values
PRIVATE_KEY=""
ENABLE_MCP=false
NETWORK=""

# Parse command line arguments
while [[ $# -gt 0 ]]; do
Expand All @@ -260,6 +262,10 @@ phases:
ENABLE_MCP=true
shift
;;
--network)
NETWORK="$2"
shift 2
;;
*)
echo "Unknown option $1"
exit 1
Expand All @@ -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."
Expand All @@ -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..."
Expand All @@ -312,6 +344,7 @@ phases:
# Create/update .env file
cat > .env << ENVEOF
CHAIN_ID=$CHAIN_ID
NETWORK_TYPE=$NETWORK_TYPE
ENVEOF

# Handle MCP configuration
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"),
Expand All @@ -481,14 +529,17 @@ phases:
{
ComponentArn: configComponent.AttrArn(),
},
{
ComponentArn: welcomeComponent.AttrArn(),
},
},
BlockDeviceMappings: &[]*awsimagebuilder.CfnImageRecipe_InstanceBlockDeviceMappingProperty{
{
DeviceName: jsii.String("/dev/sda1"),
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),
},
},
Expand Down Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions deployments/infra/stacks/docker-compose.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment on lines +59 to +71
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate CHAIN_ID when NETWORK_TYPE=custom to avoid broken init.

If NETWORK_TYPE=custom but CHAIN_ID is unset, /app/kwild setup init --chain-id "" will misconfigure. Add a guard.

-          if [ "$$NETWORK_TYPE" = "custom" ]; then
-            echo 'Creating new custom network with chain ID: '\"$$CHAIN_ID\"
+          if [ "$$NETWORK_TYPE" = "custom" ]; then
+            if [ -z "$$CHAIN_ID" ]; then
+              echo 'Error: CHAIN_ID must be set when NETWORK_TYPE=custom' >&2
+              exit 1
+            fi
+            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
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 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
# Determine network type and generate appropriate configuration
NETWORK_TYPE="$${NETWORK_TYPE:-mainnet}"
if [ "$$NETWORK_TYPE" = "custom" ]; then
# Ensure a chain ID is provided for custom networks
if [ -z "$$CHAIN_ID" ]; then
echo 'Error: CHAIN_ID must be set when NETWORK_TYPE=custom' >&2
exit 1
fi
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
Expand Down
18 changes: 15 additions & 3 deletions scripts/test-ami.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,20 @@ 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))
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..."
Expand Down Expand Up @@ -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..."
Expand Down Expand Up @@ -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..."
Expand Down
Loading