AppRunner: Reload app on configuration change

Change-Id: I2c5ea0eaf3453815c8c89c9290edba250ca4fbaa
diff --git a/apps/app-runner/main.go b/apps/app-runner/main.go
index b11a977..6aa487c 100644
--- a/apps/app-runner/main.go
+++ b/apps/app-runner/main.go
@@ -11,6 +11,7 @@
 	"path/filepath"
 	"strings"
 	"syscall"
+	"time"
 
 	"golang.org/x/crypto/ssh"
 
@@ -94,6 +95,19 @@
 	}, nil
 }
 
+func readRunConfiguration(p string) []Command {
+	r, err := os.Open(p)
+	if err != nil {
+		panic(err)
+	}
+	defer r.Close()
+	var cmds []Command
+	if err := json.NewDecoder(r).Decode(&cmds); err != nil {
+		log.Fatal(err)
+	}
+	return cmds
+}
+
 func main() {
 	flag.Parse()
 	self, ok := os.LookupEnv("SELF_IP")
@@ -121,15 +135,7 @@
 			panic(err)
 		}
 	}
-	r, err := os.Open(*runCfg)
-	if err != nil {
-		panic(err)
-	}
-	defer r.Close()
-	var cmds []Command
-	if err := json.NewDecoder(r).Decode(&cmds); err != nil {
-		panic(err)
-	}
+	cmds := readRunConfiguration(*runCfg)
 	s := NewServer(*agentMode, *port, *appId, *service, id, *repoAddr, *branch, *rootDir, signer, *appDir, cmds, self, *managerAddr)
 	go func() {
 		if err := s.Start(); err != nil {
@@ -139,8 +145,51 @@
 			os.Exit(0)
 		}
 	}()
+	go func() {
+		for {
+			time.Sleep(30 * time.Second)
+			newCmds := readRunConfiguration(*runCfg)
+			if commandsChanged(cmds, newCmds) {
+				s.UpdateRunCommands(newCmds)
+				cmds = newCmds
+			}
+		}
+	}()
 	sigChan := make(chan os.Signal, 1)
 	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
 	<-sigChan
 	s.Stop()
 }
+
+func commandsChanged(a, b []Command) bool {
+	if len(a) != len(b) {
+		return true
+	}
+	for i, x := range a {
+		y := b[i]
+		if x.Bin != y.Bin {
+			return true
+		}
+		if len(x.Args) != len(y.Args) {
+			return true
+		}
+		for j, k := range x.Args {
+			l := y.Args[j]
+			if k != l {
+				return true
+			}
+		}
+		if !*agentMode {
+			if len(x.Env) != len(y.Env) {
+				return true
+			}
+			for j, k := range x.Env {
+				l := y.Env[j]
+				if k != l {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
diff --git a/apps/app-runner/server.go b/apps/app-runner/server.go
index 44e86fc..080a134 100644
--- a/apps/app-runner/server.go
+++ b/apps/app-runner/server.go
@@ -128,6 +128,13 @@
 	}
 }
 
+func (s *Server) UpdateRunCommands(runCommands []Command) {
+	s.l.Lock()
+	defer s.l.Unlock()
+	s.runCommands = runCommands
+	s.run()
+}
+
 func (s *Server) handleQuit(w http.ResponseWriter, r *http.Request) {
 	go s.Stop()
 }
diff --git a/charts/app-runner/templates/install.yaml b/charts/app-runner/templates/install.yaml
index 8bc558b..212fc85 100644
--- a/charts/app-runner/templates/install.yaml
+++ b/charts/app-runner/templates/install.yaml
@@ -57,8 +57,8 @@
 metadata:
   name: {{ .Values.name }}-app
   namespace: {{ .Release.Namespace }}
-  annotations:
-    dodo.cloud/config-checksum: {{ sha256sum .Values.runCfg }}
+  # annotations:
+  #   dodo.cloud/config-checksum: {{ sha256sum .Values.runCfg }}
 spec:
   selector:
     matchLabels:
@@ -68,8 +68,8 @@
     metadata:
       labels:
         app: {{ .Values.name }}-app
-      annotations:
-        dodo.cloud/config-checksum: {{ sha256sum .Values.runCfg }}
+      # annotations:
+      #   dodo.cloud/config-checksum: {{ sha256sum .Values.runCfg }}
     spec:
       runtimeClassName: {{ .Values.runtimeClassName }}
       volumes:
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index 4ec89ea..c763d4b 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -451,13 +451,8 @@
 	lastCmdEnv: [...string]
 
 	runConfiguration: [{
-		bin: "npm"
-		args: ["install"]
+		bin: "npm install"
 	},
-		{
-			bin: "npm"
-			args: ["ci"]
-		},
 		for c in preBuildCommands {
 			{
 				bin:  c.bin
@@ -466,12 +461,10 @@
 			}
 		},
 		{
-			bin: "npm"
-			args: ["run", "build"]
+			bin: "npm run build"
 			env: lastCmdEnv
 		}, {
-			bin: "node"
-			args: ["./\(buildPath)/index.js"]
+			bin: "npm run start"
 			env: lastCmdEnv
 		}]
 
@@ -507,19 +500,18 @@
 		name:             "claude"
 		anthropicApiKey?: string
 	}
-	_geminiApiKey?:    string
-	_anthropicApiKey?: string
+	_llmApiKey?: string
 	if model.name == "gemini" && model.geminiApiKey != _|_ {
-		_geminiApiKey: model.geminiApiKey
+		_llmApiKey: model.geminiApiKey
 	}
 	if model.name == "gemini" && model.geminiApiKey == _|_ && input.geminiApiKey != _|_ {
-		_geminiApiKey: input.geminiApiKey
+		_llmApiKey: input.geminiApiKey
 	}
 	if model.name == "claude" && model.anthropicApiKey != _|_ {
-		_anthropicApiKey: model.anthropicApiKey
+		_llmApiKey: model.anthropicApiKey
 	}
 	if model.name == "claude" && model.anthropicApiKey == _|_ && input.anthropicApiKey != _|_ {
-		_anthropicApiKey: input.anthropicApiKey
+		_llmApiKey: input.anthropicApiKey
 	}
 
 	agentPort: 2001
@@ -552,18 +544,10 @@
 		}, {
 			name:  "DODO_API_BASE_ADDR"
 			value: input.managerAddr
-		},
-			if _geminiApiKey != _|_ {
-				name:  "GEMINI_API_KEY"
-				value: _geminiApiKey
-			},
-			if _anthropicApiKey != _|_ {
-				name:  "ANTHROPIC_API_KEY"
-				value: _anthropicApiKey
-			},
-		]
+		}]
 		...
 	}
+
 	rootDir: "/dodo/volume/\(_name)-apps"
 
 	lastCmdEnv: [...string]
@@ -571,7 +555,7 @@
 	_sessionId: input["sketch_\(name)_session_id"]
 
 	runConfiguration: [{
-		bin: "sketch -verbose -unsafe -skaband-addr=\"\" -addr=\"0.0.0.0:2001\" -model=\(model.name) -session-id=\"\(_sessionId)\""
+		bin: "sketch -verbose -unsafe -skaband-addr=\"\" -addr=\"0.0.0.0:2001\" -model=\(model.name) -llm-api-key=\"(_llmApiKey)\" -session-id=\"\(_sessionId)\""
 		env: lastCmdEnv
 	}]
 }