Ship POST /api/v1/forge/launch-template with cloud-init user-data embedding genesis snapshot hash and strain card ID; first template-tagged auth pins SpreadGeneration=0 and ParentAgentID=template.
170 lines
6.0 KiB
Go
170 lines
6.0 KiB
Go
package builder
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
JoinLaneLaunchTemplate = "launch_template"
|
|
LaunchTemplateParentAgentID = "template"
|
|
defaultLaunchTemplateAMI = "ami-0c55b159cbfafe1f0"
|
|
defaultLaunchTemplateInst = "t3.small"
|
|
launchTemplateName = "aetherforge-strain-genesis"
|
|
)
|
|
|
|
type LaunchTemplateRequest struct {
|
|
ServerURL string `json:"server_url"`
|
|
BuildID string `json:"build_id,omitempty"`
|
|
StrainCardID string `json:"strain_card_id,omitempty"`
|
|
Campaign string `json:"campaign,omitempty"`
|
|
AMIID string `json:"ami_id,omitempty"`
|
|
InstanceType string `json:"instance_type,omitempty"`
|
|
Region string `json:"region,omitempty"`
|
|
}
|
|
|
|
type LaunchTemplateResponse struct {
|
|
Success bool `json:"success"`
|
|
GenesisSnapshotHash string `json:"genesis_snapshot_hash"`
|
|
StrainCardID string `json:"strain_card_id"`
|
|
LaunchTemplateJSON string `json:"launch_template_json"`
|
|
UserDataSh string `json:"user_data_sh"`
|
|
ASGExampleJSON string `json:"asg_example_json"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type genesisSnapshotManifest struct {
|
|
Version int `json:"version"`
|
|
ServerURL string `json:"server_url"`
|
|
BuildID string `json:"build_id,omitempty"`
|
|
StrainCardID string `json:"strain_card_id,omitempty"`
|
|
JoinLane string `json:"join_lane"`
|
|
Campaign string `json:"campaign,omitempty"`
|
|
}
|
|
|
|
func GenesisSnapshotHash(m genesisSnapshotManifest) string {
|
|
m.Version = 1
|
|
m.JoinLane = JoinLaneLaunchTemplate
|
|
raw, _ := json.Marshal(m)
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func BuildLaunchTemplateArtifacts(req LaunchTemplateRequest) (LaunchTemplateResponse, error) {
|
|
serverURL := strings.TrimRight(strings.TrimSpace(req.ServerURL), "/")
|
|
if serverURL == "" {
|
|
return LaunchTemplateResponse{Success: false, Error: "server_url required"}, fmt.Errorf("server_url required")
|
|
}
|
|
buildID := strings.TrimSpace(req.BuildID)
|
|
strainCardID := strings.TrimSpace(req.StrainCardID)
|
|
campaign := strings.TrimSpace(req.Campaign)
|
|
amiID := strings.TrimSpace(req.AMIID)
|
|
if amiID == "" {
|
|
amiID = defaultLaunchTemplateAMI
|
|
}
|
|
instanceType := strings.TrimSpace(req.InstanceType)
|
|
if instanceType == "" {
|
|
instanceType = defaultLaunchTemplateInst
|
|
}
|
|
region := strings.TrimSpace(req.Region)
|
|
if region == "" {
|
|
region = "us-east-1"
|
|
}
|
|
genesisHash := GenesisSnapshotHash(genesisSnapshotManifest{
|
|
ServerURL: serverURL, BuildID: buildID, StrainCardID: strainCardID, Campaign: campaign,
|
|
})
|
|
querySuffix := buildLaunchTemplateQuerySuffix(buildID, campaign)
|
|
userData := buildLaunchTemplateUserData(serverURL, genesisHash, strainCardID, campaign, querySuffix)
|
|
userDataB64 := base64.StdEncoding.EncodeToString([]byte(userData))
|
|
ltPayload := map[string]interface{}{
|
|
"LaunchTemplateName": launchTemplateName,
|
|
"VersionDescription": fmt.Sprintf("AetherForge strain genesis snapshot %s", genesisHash[:12]),
|
|
"LaunchTemplateData": map[string]interface{}{
|
|
"ImageId": amiID, "InstanceType": instanceType, "UserData": userDataB64,
|
|
"TagSpecifications": []map[string]interface{}{{
|
|
"ResourceType": "instance",
|
|
"Tags": []map[string]string{
|
|
{"Key": "Name", "Value": "aetherforge-genesis"},
|
|
{"Key": "aetherforge:genesis-snapshot", "Value": genesisHash},
|
|
{"Key": "aetherforge:strain-card", "Value": strainCardID},
|
|
{"Key": "aetherforge:join-lane", "Value": JoinLaneLaunchTemplate},
|
|
},
|
|
}},
|
|
},
|
|
}
|
|
ltJSON, err := json.MarshalIndent(ltPayload, "", " ")
|
|
if err != nil {
|
|
return LaunchTemplateResponse{Success: false, Error: err.Error()}, err
|
|
}
|
|
asgPayload := map[string]interface{}{
|
|
"AutoScalingGroupName": "aetherforge-strain-genesis-asg",
|
|
"LaunchTemplate": map[string]interface{}{"LaunchTemplateName": launchTemplateName, "Version": "$Latest"},
|
|
"MinSize": 1, "MaxSize": 10, "DesiredCapacity": 2,
|
|
"VPCZoneIdentifier": []string{"subnet-xxxxxxxx"},
|
|
"Tags": []map[string]interface{}{
|
|
{"Key": "aetherforge:genesis-snapshot", "Value": genesisHash, "PropagateAtLaunch": true},
|
|
{"Key": "aetherforge:strain-card", "Value": strainCardID, "PropagateAtLaunch": true},
|
|
{"Key": "aetherforge:region", "Value": region, "PropagateAtLaunch": false},
|
|
},
|
|
}
|
|
asgJSON, err := json.MarshalIndent(asgPayload, "", " ")
|
|
if err != nil {
|
|
return LaunchTemplateResponse{Success: false, Error: err.Error()}, err
|
|
}
|
|
return LaunchTemplateResponse{
|
|
Success: true, GenesisSnapshotHash: genesisHash, StrainCardID: strainCardID,
|
|
LaunchTemplateJSON: string(ltJSON), UserDataSh: userData, ASGExampleJSON: string(asgJSON),
|
|
}, nil
|
|
}
|
|
|
|
func buildLaunchTemplateQuerySuffix(buildID, campaign string) string {
|
|
var parts []string
|
|
if buildID != "" {
|
|
parts = append(parts, "pin="+buildID)
|
|
}
|
|
if campaign != "" {
|
|
parts = append(parts, "c="+campaign)
|
|
}
|
|
sort.Strings(parts)
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
return "?" + strings.Join(parts, "&")
|
|
}
|
|
|
|
func buildLaunchTemplateUserData(serverURL, genesisHash, strainCardID, campaign, querySuffix string) string {
|
|
var b strings.Builder
|
|
b.WriteString("#!/bin/bash\n# AetherForge EC2 launch-template strain genesis\n")
|
|
b.WriteString("set -euo pipefail\nexport AETHER_PARENT_AGENT_ID=")
|
|
b.WriteString(jsonStringLiteral(LaunchTemplateParentAgentID))
|
|
b.WriteString("\nexport AETHER_SPREAD_GENERATION=0\nexport AETHER_GENESIS_SNAPSHOT_HASH=")
|
|
b.WriteString(jsonStringLiteral(genesisHash))
|
|
b.WriteString("\n")
|
|
if strainCardID != "" {
|
|
b.WriteString("export AETHER_STRAIN_CARD_ID=")
|
|
b.WriteString(jsonStringLiteral(strainCardID))
|
|
b.WriteString("\n")
|
|
}
|
|
if campaign != "" {
|
|
b.WriteString("export AETHER_CAMPAIGN=")
|
|
b.WriteString(jsonStringLiteral(campaign))
|
|
b.WriteString("\nexport AETHER_UTM=")
|
|
b.WriteString(jsonStringLiteral(campaign))
|
|
b.WriteString("\n")
|
|
}
|
|
b.WriteString("\ncurl -fsSL ")
|
|
b.WriteString(jsonStringLiteral(serverURL + "/install.sh" + querySuffix))
|
|
b.WriteString(" | bash\n")
|
|
return b.String()
|
|
}
|
|
|
|
func jsonStringLiteral(s string) string {
|
|
raw, _ := json.Marshal(s)
|
|
return string(raw)
|
|
}
|