DodoApp: Support PHP 8.2

Change-Id: I7cfe082c66a0efe0b3b9c85433a96623466ced5c
diff --git a/apps/app-runner/Dockerfile.php.8.2.apache b/apps/app-runner/Dockerfile.php.8.2.apache
new file mode 100644
index 0000000..34a11a7
--- /dev/null
+++ b/apps/app-runner/Dockerfile.php.8.2.apache
@@ -0,0 +1,6 @@
+FROM php:8.2-apache-bookworm
+ARG TARGETARCH
+
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
+
+COPY app-runner_${TARGETARCH} /usr/bin/app-runner
diff --git a/apps/app-runner/Makefile b/apps/app-runner/Makefile
index 11a659d..94f98ab 100644
--- a/apps/app-runner/Makefile
+++ b/apps/app-runner/Makefile
@@ -4,6 +4,7 @@
 manifest_dest_golang_1_22_0=docker://docker.io/$(repo_name)/app-runner:golang-1.22.0
 manifest_dest_golang_1_20_0=docker://docker.io/$(repo_name)/app-runner:golang-1.20.0
 manifest_dest_hugo_latest=docker://docker.io/$(repo_name)/app-runner:hugo-latest
+manifest_dest_php_8_2_apache=docker://docker.io/$(repo_name)/app-runner:php-8.2-apache
 endif
 
 clean:
@@ -68,5 +69,20 @@
 	$(podman) manifest push $(repo_name)/app-runner:hugo-latest $(manifest_dest_hugo_latest)
 	$(podman) manifest rm $(repo_name)/app-runner:hugo-latest
 
+# PHP
+
+push_php_8_2_apache_arm64: clean build_arm64
+	$(podman) build --platform linux/arm64 --tag=$(repo_name)/app-runner:php-8.2-apache-arm64 -f Dockerfile.php.8.2.apache .
+	$(podman) push $(repo_name)/app-runner:php-8.2-apache-arm64
+
+push_php_8_2_apache_amd64: clean build_amd64
+	$(podman) build --platform linux/amd64 --tag=$(repo_name)/app-runner:php-8.2-apache-amd64 -f Dockerfile.php.8.2.apache .
+	$(podman) push $(repo_name)/app-runner:php-8.2-apache-amd64
+
+push_php_8_2_apache: push_php_8_2_apache_arm64 push_php_8_2_apache_amd64
+	$(podman) manifest create $(repo_name)/app-runner:php-8.2-apache $(repo_name)/app-runner:php-8.2-apache-arm64 $(repo_name)/app-runner:php-8.2-apache-amd64
+	$(podman) manifest push $(repo_name)/app-runner:php-8.2-apache $(manifest_dest_php_8_2_apache)
+	$(podman) manifest rm $(repo_name)/app-runner:php-8.2-apache
+
 # all
-push: push_golang_1_22_0 push_golang_1_20_0 push_hugo
+push: push_golang_1_22_0 push_golang_1_20_0 push_hugo push_php_8_2_apache
diff --git a/charts/app-runner/templates/install.yaml b/charts/app-runner/templates/install.yaml
index acd0b3d..f89cc79 100644
--- a/charts/app-runner/templates/install.yaml
+++ b/charts/app-runner/templates/install.yaml
@@ -94,7 +94,7 @@
         - app-runner
         - --port=3000
         - --app-id={{ .Values.appId }}
-        - --app-dir=/dodo-app
+        - --app-dir={{ .Values.appDir }}
         - --repo-addr={{ .Values.repoAddr }}
         - --ssh-key=/pcloud/ssh-key/private
         - --run-cfg=/pcloud/config/run
diff --git a/core/installer/app_configs/app_global_env.cue b/core/installer/app_configs/app_global_env.cue
index f5858e4..52e492d 100644
--- a/core/installer/app_configs/app_global_env.cue
+++ b/core/installer/app_configs/app_global_env.cue
@@ -22,7 +22,6 @@
 }
 
 #Networks: {
-	public: #Network
 	...
 }
 
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index 447e45d..46af283 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -47,9 +47,11 @@
 
 #GoAppTmpl: {
 	type: _goVer1220 | _goVer1200
-	run: string
+	run: string | *"main.go"
 	ingress: #AppIngress
 	volumes: #Volumes
+	port: int | *8080
+	rootDir: _appDir
 
 	runConfiguration: [{
 		bin: "/usr/local/go/bin/go",
@@ -86,6 +88,8 @@
 	type: _hugoLatest
 	ingress: #AppIngress
 	volumes: {}
+	port: int | *8080
+	rootDir: _appDir
 
 	runConfiguration: [{
 		bin: "/usr/bin/hugo",
@@ -96,7 +100,7 @@
 			"server",
 			"--watch=false",
 			"--bind=0.0.0.0",
-			"--port=\(_appPort)",
+			"--port=\(port)",
 			"--baseURL=\(ingress.baseURL)",
 			"--appendPort=false",
     	]
@@ -105,7 +109,28 @@
 
 #HugoApp: #HugoAppTmpl
 
-#App: #GoApp | #HugoApp
+// PHP app
+
+#PHPAppTmpl: {
+	type: "php:8.2-apache"
+	ingress: #AppIngress
+	volumes: {}
+	port: int | *80
+	rootDir: "/var/www/html"
+
+	runConfiguration: [{
+		bin: "/usr/local/bin/apache2-foreground",
+		env: [
+			for k, v in volumes {
+				"DODO_VOLUME_\(strings.ToUpper(k))=/dodo-volume/\(v.name)"
+			}
+	    ]
+	}]
+}
+
+#PHPApp: #PHPAppTmpl
+
+#App: #GoApp | #HugoApp | #PHPApp
 
 app: #App
 
@@ -154,8 +179,8 @@
 				pullPolicy: images.app.pullPolicy
 			}
 			runtimeClassName: "untrusted-external" // TODO(gio): make this part of the infra config
-			appPort: _appPort
-			appDir: _appDir
+			appPort: _app.port
+			appDir: _app.rootDir
 			appId: input.appId
 			repoAddr: input.repoAddr
 			sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
@@ -172,4 +197,3 @@
 }
 
 _appDir: "/dodo-app"
-_appPort: 8080
diff --git a/core/installer/welcome/app-tmpl/php-8.2-apache/app.cue.gotmpl b/core/installer/welcome/app-tmpl/php-8.2-apache/app.cue.gotmpl
new file mode 100755
index 0000000..fa925fe
--- /dev/null
+++ b/core/installer/welcome/app-tmpl/php-8.2-apache/app.cue.gotmpl
@@ -0,0 +1,8 @@
+app: {
+	type: "php:8.2-apache"
+	ingress: {
+		network: "{{ .Network.Name }}"
+		subdomain: "{{ .Subdomain }}"
+		auth: enabled: false
+	}
+}
diff --git a/core/installer/welcome/app-tmpl/php-8.2-apache/index.php b/core/installer/welcome/app-tmpl/php-8.2-apache/index.php
new file mode 100644
index 0000000..bb93008
--- /dev/null
+++ b/core/installer/welcome/app-tmpl/php-8.2-apache/index.php
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>dodo app: php 8.2</title>
+    </head>
+    <body>
+        <?php echo '<p>Hello from dodo: app</p>'; ?>
+		// <?php phpinfo(); ?>
+    </body>
+</html>
diff --git a/core/installer/welcome/app_tmpl.go b/core/installer/welcome/app_tmpl.go
index dae56f8..33f8d8d 100644
--- a/core/installer/welcome/app_tmpl.go
+++ b/core/installer/welcome/app_tmpl.go
@@ -4,6 +4,7 @@
 	"fmt"
 	"io"
 	"io/fs"
+	"sort"
 	"strings"
 	"text/template"
 
@@ -46,6 +47,16 @@
 	for t := range s.tmpls {
 		ret = append(ret, t)
 	}
+	sort.Slice(ret, func(i, j int) bool {
+		a := strings.SplitN(ret[i], ":", 2)
+		b := strings.SplitN(ret[j], ":", 2)
+		langCmp := strings.Compare(a[0], b[0])
+		if langCmp != 0 {
+			return langCmp < 0
+		}
+		// TODO(gio): compare semver?
+		return strings.Compare(a[1], b[1]) > 0
+	})
 	return ret
 }
 
diff --git a/core/installer/welcome/app_tmpl_test.go b/core/installer/welcome/app_tmpl_test.go
index fca6d12..ddd142c 100644
--- a/core/installer/welcome/app_tmpl_test.go
+++ b/core/installer/welcome/app_tmpl_test.go
@@ -81,3 +81,22 @@
 		t.Fatal(err)
 	}
 }
+
+func TestAppTmplPHP82(t *testing.T) {
+	d, err := fs.Sub(appTmpl, "app-tmpl")
+	if err != nil {
+		t.Fatal(err)
+	}
+	store, err := NewAppTmplStoreFS(d)
+	if err != nil {
+		t.Fatal(err)
+	}
+	a, err := store.Find("php-8.2-apache")
+	if err != nil {
+		t.Fatal(err)
+	}
+	out := soft.NewBillyRepoFS(memfs.New())
+	if err := a.Render(network, "testapp", out); err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index afcd627..87b003a 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -44,8 +44,6 @@
 	userCtx        = "user"
 )
 
-var types = []string{"golang:1.22.0", "golang:1.20.0", "hugo:latest"}
-
 type dodoAppTmplts struct {
 	index     *template.Template
 	appStatus *template.Template
@@ -373,6 +371,10 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
+	var types []string
+	for _, t := range s.appTmpls.Types() {
+		types = append(types, strings.Replace(t, "-", ":", 1))
+	}
 	data := statusData{apps, networks, types}
 	if err := s.tmplts.index.Execute(w, data); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -869,7 +871,7 @@
 }
 
 func (s *DodoAppServer) initRepo(repo soft.RepoIO, appType string, network installer.Network, subdomain string) error {
-	appType = strings.ReplaceAll(appType, ":", "-")
+	appType = strings.Replace(appType, ":", "-", 1)
 	appTmpl, err := s.appTmpls.Find(appType)
 	if err != nil {
 		return err
@@ -925,9 +927,8 @@
 		}
 	}
 	for _, t := range s.appTmpls.Types() {
-		ret.Types = append(ret.Types, strings.ReplaceAll(t, "-", ":"))
+		ret.Types = append(ret.Types, strings.Replace(t, "-", ":", 1))
 	}
-	w.Header().Set("Access-Control-Allow-Origin", "*")
 	if err := json.NewEncoder(w).Encode(ret); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return