Skip to content

Instantly share code, notes, and snippets.

@cds-amal
Created July 12, 2025 03:41
Show Gist options
  • Save cds-amal/13fd5ff8cc3a1e67a231b6e58807a5e1 to your computer and use it in GitHub Desktop.
Save cds-amal/13fd5ff8cc3a1e67a231b6e58807a5e1 to your computer and use it in GitHub Desktop.
package cmd
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/briandowns/spinner"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
var deployCmd = &cobra.Command{
Use: "deploy [profile]",
Short: "Deploy DIN-AVS with a single command",
Long: `Deploy DIN-AVS infrastructure with intelligent defaults and automatic setup.
Examples:
dorch deploy # Auto-detect profile and deploy
dorch deploy devnet # Deploy devnet profile
dorch deploy testnet02 # Deploy testnet02 profile`,
Args: cobra.MaximumNArgs(1),
RunE: runDeploy,
}
var (
deployTarget string
skipChecks bool
skipValidation bool
forceRebuild bool
noBrowser bool
deployVerbose bool
deployDryRun bool
)
func init() {
deployCmd.Flags().StringVar(&deployTarget, "target", "", "Deployment target (default: profile default)")
deployCmd.Flags().BoolVar(&skipChecks, "skip-checks", false, "Skip prerequisite checks")
deployCmd.Flags().BoolVar(&skipValidation, "skip-validation", false, "Skip profile validation")
deployCmd.Flags().BoolVar(&forceRebuild, "force-rebuild", false, "Force container rebuild")
deployCmd.Flags().BoolVar(&noBrowser, "no-browser", false, "Don't open browser after deployment")
deployCmd.Flags().BoolVarP(&deployVerbose, "verbose", "v", false, "Enable verbose output")
deployCmd.Flags().BoolVar(&deployDryRun, "dry-run", false, "Show what would be deployed without executing")
rootCmd.AddCommand(deployCmd)
}
type DeploymentStep struct {
Name string
Description string
Execute func(ctx *DeploymentContext) error
CanFail bool
}
type DeploymentContext struct {
Profile string
Target string
DinDockerDir string
DinAvsDir string
ChainRegistryCmd string
StartTime time.Time
Spinner *spinner.Spinner
Verbose bool
}
func runDeploy(cmd *cobra.Command, args []string) error {
// Determine profile
var profile string
if len(args) > 0 {
profile = args[0]
} else {
// Auto-detect profile
profile = detectDeploymentProfile()
color.Cyan("Auto-detected profile: %s\n", profile)
}
// Initialize deployment context
ctx := &DeploymentContext{
Profile: profile,
Target: deployTarget,
DinDockerDir: getDinDockerDir(),
StartTime: time.Now(),
Verbose: deployVerbose,
}
// Set up paths
ctx.DinAvsDir = getEnvOrDefault("DIN_AVS_DIR", filepath.Join(os.Getenv("HOME"), "work/din-workspace/din-avs"))
ctx.ChainRegistryCmd = filepath.Join(ctx.DinDockerDir, "chain-registry/chain-registry")
// Dry run mode
if deployDryRun {
return showDryRun(ctx)
}
// Display deployment header
fmt.Println()
color.Green("🚀 DIN-AVS Deployment Orchestrator")
fmt.Println(strings.Repeat("=", 40))
fmt.Printf("Profile: %s\n", color.YellowString(ctx.Profile))
if ctx.Target != "" {
fmt.Printf("Target: %s\n", color.YellowString(ctx.Target))
}
fmt.Printf("Time: %s\n", ctx.StartTime.Format("2006-01-02 15:04:05"))
fmt.Println(strings.Repeat("=", 40))
fmt.Println()
// Define deployment steps
steps := []DeploymentStep{
{
Name: "Prerequisites",
Description: "Checking system requirements",
Execute: checkPrerequisites,
CanFail: false,
},
{
Name: "Profile Validation",
Description: "Validating deployment profile",
Execute: validateProfile,
CanFail: skipValidation,
},
{
Name: "Anvil Setup",
Description: "Starting local Ethereum node",
Execute: setupAnvil,
CanFail: false,
},
{
Name: "Container Check",
Description: "Checking container rebuild requirements",
Execute: checkContainerRebuild,
CanFail: false,
},
{
Name: "Contract Deployment",
Description: "Deploying smart contracts",
Execute: deployContracts,
CanFail: false,
},
{
Name: "Environment Setup",
Description: "Loading contract addresses",
Execute: loadContractAddresses,
CanFail: false,
},
{
Name: "Docker Configuration",
Description: "Generating Docker configuration",
Execute: generateDockerConfig,
CanFail: false,
},
{
Name: "Service Startup",
Description: "Building and starting services",
Execute: startServices,
CanFail: false,
},
{
Name: "Health Checks",
Description: "Verifying service health",
Execute: performHealthChecks,
CanFail: false,
},
{
Name: "Deployment Verification",
Description: "Finalizing deployment",
Execute: verifyDeployment,
CanFail: false,
},
}
// Execute deployment steps
for i, step := range steps {
if skipChecks && step.Name == "Prerequisites" {
continue
}
if skipValidation && step.Name == "Profile Validation" {
continue
}
if err := executeStep(ctx, step, i+1, len(steps)); err != nil {
if !step.CanFail {
color.Red("\n❌ Deployment failed at step: %s", step.Name)
return err
}
color.Yellow("⚠️ Warning: %s (continuing)", err.Error())
}
}
// Deployment complete
duration := time.Since(ctx.StartTime)
fmt.Println()
color.Green("✅ Deployment completed successfully!")
fmt.Printf("Total time: %s\n", duration.Round(time.Second))
// Open browser if requested
if !noBrowser {
openBrowser("http://localhost:8080")
}
// Show helpful commands
showPostDeploymentInfo(ctx)
return nil
}
func executeStep(ctx *DeploymentContext, step DeploymentStep, current, total int) error {
// Progress indicator
progress := fmt.Sprintf("[%d/%d]", current, total)
color.Blue("%s %s", progress, step.Description)
// Create spinner for visual feedback
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
s.Suffix = " "
ctx.Spinner = s
if !ctx.Verbose {
s.Start()
}
// Execute step
err := step.Execute(ctx)
if !ctx.Verbose {
s.Stop()
}
if err != nil {
color.Red(" ❌")
return err
}
color.Green(" ✓")
return nil
}
func detectDeploymentProfile() string {
// Use chain-registry detect command
cmd := exec.Command("chain-registry", "current", "detect")
output, err := cmd.Output()
if err == nil {
profile := strings.TrimSpace(string(output))
if profile != "" {
return profile
}
}
// Fallback to devnet
return "devnet"
}
func getDinDockerDir() string {
// Try to find din-docker directory
cwd, err := os.Getwd()
if err == nil {
// Check if we're already in din-docker
if strings.Contains(cwd, "din-docker") {
return cwd
}
// Check parent directories
dir := cwd
for i := 0; i < 5; i++ {
if filepath.Base(dir) == "din-docker" {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
}
// Default
return filepath.Join(os.Getenv("HOME"), "work/din-workspace/din-docker")
}
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func showDryRun(ctx *DeploymentContext) error {
fmt.Println("🔍 Dry Run Mode - Showing deployment plan")
fmt.Println()
fmt.Printf("Profile: %s\n", ctx.Profile)
fmt.Printf("Target: %s\n", ctx.Target)
fmt.Printf("DIN Docker Dir: %s\n", ctx.DinDockerDir)
fmt.Printf("DIN AVS Dir: %s\n", ctx.DinAvsDir)
fmt.Println()
fmt.Println("Steps that would be executed:")
fmt.Println("1. Check prerequisites (Docker, tools)")
fmt.Println("2. Validate profile configuration")
fmt.Println("3. Start Anvil if not running")
fmt.Println("4. Check and rebuild containers if needed")
fmt.Println("5. Deploy smart contracts")
fmt.Println("6. Load contract addresses")
fmt.Println("7. Generate Docker configuration")
fmt.Println("8. Build and start services")
fmt.Println("9. Perform health checks")
fmt.Println("10. Verify deployment")
return nil
}
func checkPrerequisites(ctx *DeploymentContext) error {
// Check required commands
requiredCmds := []string{"docker", "docker-compose", "yq", "jq", "txtx"}
for _, cmd := range requiredCmds {
if _, err := exec.LookPath(cmd); err != nil {
return fmt.Errorf("%s not found in PATH", cmd)
}
}
// Check Docker daemon
cmd := exec.Command("docker", "info")
if err := cmd.Run(); err != nil {
return fmt.Errorf("Docker daemon not running")
}
// Check chain-registry
if _, err := os.Stat(ctx.ChainRegistryCmd); err != nil {
// Build it
buildCmd := exec.Command("make", "build")
buildCmd.Dir = filepath.Dir(ctx.ChainRegistryCmd)
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("failed to build chain-registry: %w", err)
}
}
// Check din-avs directory
if _, err := os.Stat(ctx.DinAvsDir); err != nil {
return fmt.Errorf("din-avs directory not found at %s", ctx.DinAvsDir)
}
return nil
}
func validateProfile(ctx *DeploymentContext) error {
validateScript := filepath.Join(ctx.DinDockerDir, "scripts/validate-profile.sh")
if _, err := os.Stat(validateScript); err != nil {
// Script doesn't exist, skip validation
return nil
}
cmd := exec.Command(validateScript, ctx.Profile)
cmd.Dir = ctx.DinDockerDir
if ctx.Verbose {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
return cmd.Run()
}
func setupAnvil(ctx *DeploymentContext) error {
// Check if Anvil is already running
checkCmd := exec.Command("lsof", "-i", ":8545")
if err := checkCmd.Run(); err == nil {
// Anvil already running
return nil
}
// Start Anvil
envFile := filepath.Join(ctx.DinDockerDir, ".env.anvil")
mnemonic := "chalk floor identify cruise endless truck gauge swing noble slow swing stock"
forkURL := "https://sepolia.infura.io/v3/cc320ed2842746fdb056e717ad8fff7b"
// Load from env file if exists
if data, err := os.ReadFile(envFile); err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "MNEMONIC=") {
mnemonic = strings.TrimPrefix(line, "MNEMONIC=")
} else if strings.HasPrefix(line, "FORK_URL=") {
forkURL = strings.TrimPrefix(line, "FORK_URL=")
}
}
}
// Start Anvil in background
anvilCmd := exec.Command("anvil",
"--host", "0.0.0.0",
"--port", "8545",
"-a", "26",
"--balance", "1000",
"--fork-url", forkURL,
"-m", mnemonic,
)
if err := anvilCmd.Start(); err != nil {
return fmt.Errorf("failed to start Anvil: %w", err)
}
// Save PID
pidFile := filepath.Join(ctx.DinDockerDir, ".anvil.pid")
os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", anvilCmd.Process.Pid)), 0644)
// Wait for Anvil to be ready
time.Sleep(5 * time.Second)
return nil
}
func checkContainerRebuild(ctx *DeploymentContext) error {
if forceRebuild {
return cleanupContainers(ctx)
}
// Check using chain-registry manifest
cmd := exec.Command(ctx.ChainRegistryCmd, "manifest", "check",
"--env", ctx.Profile,
"--profile", ctx.Profile,
"--target", ctx.Target,
"--profile-dir", filepath.Join(ctx.DinDockerDir, "op-config", ctx.Profile),
)
output, err := cmd.Output()
if err != nil || strings.TrimSpace(string(output)) == "REBUILD_REQUIRED" {
return cleanupContainers(ctx)
}
return nil
}
func cleanupContainers(ctx *DeploymentContext) error {
// Stop containers
stopCmd := exec.Command("docker", "compose", "down", "--remove-orphans")
stopCmd.Dir = ctx.DinDockerDir
stopCmd.Run() // Ignore errors
if forceRebuild {
// Remove images
removeCmd := exec.Command("docker", "compose", "down", "--rmi", "local")
removeCmd.Dir = ctx.DinDockerDir
removeCmd.Run() // Ignore errors
}
return nil
}
func deployContracts(ctx *DeploymentContext) error {
// Update state
updateDeploymentState(ctx, "contracts", "deploying")
// Determine target if not specified
if ctx.Target == "" {
ctx.Target = getDefaultTarget(ctx.Profile)
}
// Run deployment
deployScript := filepath.Join(ctx.DinDockerDir, "Justfile")
cmd := exec.Command("just", "deploy", ctx.Profile, ctx.Target)
cmd.Dir = ctx.DinDockerDir
if ctx.Verbose {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Capture output for error reporting
output, err := cmd.CombinedOutput()
if err != nil {
updateDeploymentState(ctx, "contracts", "failed")
// Show last 20 lines on error
lines := strings.Split(string(output), "\n")
start := len(lines) - 20
if start < 0 {
start = 0
}
for i := start; i < len(lines); i++ {
fmt.Fprintln(os.Stderr, lines[i])
}
return fmt.Errorf("contract deployment failed")
}
updateDeploymentState(ctx, "contracts", "deployed")
return nil
}
func loadContractAddresses(ctx *DeploymentContext) error {
// Run load script
loadScript := filepath.Join(ctx.DinDockerDir, "scripts/load-contracts-env.sh")
cmd := exec.Command(loadScript)
cmd.Dir = ctx.DinDockerDir
cmd.Env = append(os.Environ(), fmt.Sprintf("REGISTRY_ENV=%s", ctx.Profile))
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to load contract addresses: %w", err)
}
// Verify .env.contracts exists
envFile := filepath.Join(ctx.DinDockerDir, ".env.contracts")
if _, err := os.Stat(envFile); err != nil {
return fmt.Errorf(".env.contracts not generated")
}
return nil
}
func generateDockerConfig(ctx *DeploymentContext) error {
updateDeploymentState(ctx, "docker-config", "generating")
generateScript := filepath.Join(ctx.DinDockerDir, "scripts/generate-from-profile.sh")
cmd := exec.Command(generateScript, ctx.Profile)
cmd.Dir = ctx.DinDockerDir
if err := cmd.Run(); err != nil {
updateDeploymentState(ctx, "docker-config", "failed")
return fmt.Errorf("failed to generate Docker configuration")
}
// Verify docker-compose.yml exists
composeFile := filepath.Join(ctx.DinDockerDir, "docker-compose.yml")
if _, err := os.Stat(composeFile); err != nil {
return fmt.Errorf("docker-compose.yml not generated")
}
updateDeploymentState(ctx, "docker-config", "generated")
return nil
}
func startServices(ctx *DeploymentContext) error {
updateDeploymentState(ctx, "services", "building")
// Build bn254-rs image if needed
if err := buildBN254Image(ctx); err != nil {
return err
}
// Load contract addresses for build
envFile := filepath.Join(ctx.DinDockerDir, ".env.contracts")
if data, err := os.ReadFile(envFile); err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
os.Setenv(parts[0], parts[1])
}
}
}
// Build services
buildCmd := exec.Command("docker", "compose", "--profile", "ui", "build")
buildCmd.Dir = ctx.DinDockerDir
if ctx.Verbose {
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
}
if err := buildCmd.Run(); err != nil {
updateDeploymentState(ctx, "services", "failed")
return fmt.Errorf("failed to build services")
}
// Start services
upCmd := exec.Command("docker", "compose", "--profile", "ui", "up", "-d")
upCmd.Dir = ctx.DinDockerDir
if err := upCmd.Run(); err != nil {
updateDeploymentState(ctx, "services", "failed")
return fmt.Errorf("failed to start services")
}
updateDeploymentState(ctx, "services", "running")
return nil
}
func buildBN254Image(ctx *DeploymentContext) error {
// Check if image exists
checkCmd := exec.Command("docker", "image", "inspect", "bn254-rs:latest")
if err := checkCmd.Run(); err == nil {
return nil // Image exists
}
// Build image
buildScript := filepath.Join(ctx.DinDockerDir, "scripts/build-bn254-image.sh")
if _, err := os.Stat(buildScript); err == nil {
cmd := exec.Command(buildScript)
cmd.Dir = ctx.DinDockerDir
return cmd.Run()
}
return fmt.Errorf("bn254-rs image not found and build script missing")
}
func performHealthChecks(ctx *DeploymentContext) error {
services := []struct {
Name string
URL string
}{
{"gateway", "http://localhost:8080/health"},
{"kms", "http://localhost:3000/health"},
{"frontend", "http://localhost:8080"},
}
// Wait a bit for services to start
time.Sleep(5 * time.Second)
for _, service := range services {
// Simple HTTP check
cmd := exec.Command("curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", service.URL)
output, err := cmd.Output()
if err != nil || string(output) != "200" {
// Try docker ps to see if container is running
psCmd := exec.Command("docker", "ps", "--filter", fmt.Sprintf("name=%s", service.Name), "--format", "{{.Names}}")
psOutput, _ := psCmd.Output()
if strings.TrimSpace(string(psOutput)) == "" {
return fmt.Errorf("service %s not running", service.Name)
}
}
// Update container status
updateContainerStatus(ctx, service.Name, true)
}
return nil
}
func verifyDeployment(ctx *DeploymentContext) error {
updateDeploymentState(ctx, "deployment", "completed")
// Save manifest
saveCmd := exec.Command(ctx.ChainRegistryCmd, "manifest", "save",
"--env", ctx.Profile,
"--profile", ctx.Profile,
"--target", ctx.Target,
"--profile-dir", filepath.Join(ctx.DinDockerDir, "op-config", ctx.Profile),
)
saveCmd.Run() // Ignore errors
// Update current deployment
setCurrentCmd := exec.Command(ctx.ChainRegistryCmd, "current", "set",
"--profile", ctx.Profile,
"--status", "deployed",
)
setCurrentCmd.Run()
return nil
}
func updateDeploymentState(ctx *DeploymentContext, phase, status string) {
cmd := exec.Command(ctx.ChainRegistryCmd, "state", "update",
"--env", ctx.Profile,
"--phase", phase,
"--status", status,
)
cmd.Run() // Ignore errors
}
func updateContainerStatus(ctx *DeploymentContext, container string, running bool) {
args := []string{"state", "update", "--env", ctx.Profile, "--container", container}
if running {
args = append(args, "--running")
}
cmd := exec.Command(ctx.ChainRegistryCmd, args...)
cmd.Run() // Ignore errors
}
func getDefaultTarget(profile string) string {
// Read deploy-targets.yaml
targetsFile := filepath.Join(getDinDockerDir(), "deploy-targets.yaml")
if _, err := os.Stat(targetsFile); err == nil {
cmd := exec.Command("yq", "-r", fmt.Sprintf(".profile_defaults.%s // \"local\"", profile), targetsFile)
if output, err := cmd.Output(); err == nil {
return strings.TrimSpace(string(output))
}
}
return "local"
}
func openBrowser(url string) {
time.Sleep(2 * time.Second) // Give services time to fully start
var cmd *exec.Cmd
switch {
case isWSL():
cmd = exec.Command("cmd.exe", "/c", "start", url)
case isMac():
cmd = exec.Command("open", url)
default:
cmd = exec.Command("xdg-open", url)
}
cmd.Run() // Ignore errors
}
func isWSL() bool {
if data, err := os.ReadFile("/proc/version"); err == nil {
return strings.Contains(strings.ToLower(string(data)), "microsoft")
}
return false
}
func isMac() bool {
return exec.Command("uname", "-s").Output()[0] == 'D' // Darwin
}
func showPostDeploymentInfo(ctx *DeploymentContext) {
fmt.Println()
color.Cyan("📋 Next Steps:")
fmt.Println("1. Navigate to http://localhost:8080")
fmt.Println("2. Connect your wallet using 'Sign In'")
fmt.Println("3. Follow the operator onboarding flow")
fmt.Println()
color.Cyan("🛠️ Useful Commands:")
fmt.Printf(" %s - Check deployment status\n", color.YellowString("just s"))
fmt.Printf(" %s - View service logs\n", color.YellowString("just l"))
fmt.Printf(" %s - Monitor transactions\n", color.YellowString("just m"))
fmt.Printf(" %s - Stop all services\n", color.YellowString("just stop"))
fmt.Printf(" %s - View state\n", color.YellowString("chain-registry state get --env %s", ctx.Profile))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment