launcher: application launcher

Change-Id: I81d49a0651702dc821d683d6a4b3bbff6af3c753
diff --git a/.gitignore b/.gitignore
index 671d18e..ed1e5c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
 main
 *_arm64
 *_amd64
+.vscode/
diff --git a/charts/launcher/.helmingonre b/charts/launcher/.helmingonre
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/charts/launcher/.helmingonre
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/charts/launcher/Chart.yaml b/charts/launcher/Chart.yaml
new file mode 100644
index 0000000..494c33b
--- /dev/null
+++ b/charts/launcher/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: launcher
+description: A Helm chart for PCloud Launcher
+type: application
+version: 0.0.1
+appVersion: "0.0.1"
diff --git a/charts/launcher/templates/install.yaml b/charts/launcher/templates/install.yaml
new file mode 100644
index 0000000..ee4b215
--- /dev/null
+++ b/charts/launcher/templates/install.yaml
@@ -0,0 +1,41 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: launcher
+  namespace: {{ .Release.Namespace }}
+spec:
+  type: ClusterIP
+  selector:
+    app: launcher
+  ports:
+    - name: {{ .Values.portName }}
+      protocol: TCP
+      port: 80
+      targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: launcher
+  namespace: {{ .Release.Namespace }}
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: launcher
+  template:
+    metadata:
+      labels:
+        app: launcher
+    spec:
+      containers:
+      - name: launcher
+        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
+        ports:
+        - name: http
+          containerPort: 8080
+          protocol: TCP
+        command:
+        - launcher
+        - --port=8080
+        - --logoutUrl={{ .Values.logoutUrl }}
diff --git a/charts/launcher/values.yaml b/charts/launcher/values.yaml
new file mode 100644
index 0000000..159ee97
--- /dev/null
+++ b/charts/launcher/values.yaml
@@ -0,0 +1,6 @@
+image:
+  repository: giolekva/launcher
+  tag: latest
+  pullPolicy: Always
+portName: http
+logoutUrl: logout.example.com
diff --git a/core/installer/.gitignore b/core/installer/.gitignore
index ef7fc43..04fadb1 100644
--- a/core/installer/.gitignore
+++ b/core/installer/.gitignore
@@ -1 +1 @@
-pcloud
\ No newline at end of file
+pcloud
diff --git a/core/installer/Makefile b/core/installer/Makefile
index 89a5715..5280a89 100644
--- a/core/installer/Makefile
+++ b/core/installer/Makefile
@@ -46,6 +46,9 @@
 rewrite:
 	./pcloud rewrite --ssh-key=/Users/lekva/.ssh/id_ed25519 --repo-addr=ssh://localhost:2222/config
 
+launcher:
+	./pcloud launcher --port=9090 --logout-url=http://localhost:8080
+
 ## installer image
 build_arm64: export CGO_ENABLED=0
 build_arm64: export GO111MODULE=on
diff --git a/core/installer/cmd/launcher.go b/core/installer/cmd/launcher.go
new file mode 100644
index 0000000..d86196b
--- /dev/null
+++ b/core/installer/cmd/launcher.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/giolekva/pcloud/core/installer/welcome"
+	"github.com/spf13/cobra"
+)
+
+var launcherFlags struct {
+	logoutUrl string
+	port      int
+}
+
+func launcherCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:  "launcher",
+		RunE: launcherCmdRun,
+	}
+	cmd.Flags().IntVar(
+		&launcherFlags.port,
+		"port",
+		8080,
+		"",
+	)
+	cmd.Flags().StringVar(
+		&launcherFlags.logoutUrl,
+		"logout-url",
+		"",
+		"",
+	)
+	return cmd
+}
+
+func launcherCmdRun(cmd *cobra.Command, args []string) error {
+	s, err := welcome.NewLauncherServer(
+		launcherFlags.port,
+		launcherFlags.logoutUrl,
+		&welcome.FakeAppDirectory{},
+	)
+	if err != nil {
+		return fmt.Errorf("failed to create LauncherServer: %v", err)
+	}
+	s.Start()
+	return nil
+}
diff --git a/core/installer/cmd/main.go b/core/installer/cmd/main.go
index 1e4ec1d..5b05381 100644
--- a/core/installer/cmd/main.go
+++ b/core/installer/cmd/main.go
@@ -27,6 +27,7 @@
 	rootCmd.AddCommand(envManagerCmd())
 	rootCmd.AddCommand(welcomeCmd())
 	rootCmd.AddCommand(rewriteCmd())
+	rootCmd.AddCommand(launcherCmd())
 }
 
 func main() {
diff --git a/core/installer/go.mod b/core/installer/go.mod
index dc4d4e4..8e173f2 100644
--- a/core/installer/go.mod
+++ b/core/installer/go.mod
@@ -21,7 +21,7 @@
 	github.com/libdns/libdns v0.2.2
 	github.com/miekg/dns v1.1.58
 	github.com/spf13/cobra v1.8.0
-	golang.org/x/crypto v0.21.0
+	golang.org/x/crypto v0.22.0
 	golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
 	helm.sh/helm/v3 v3.14.3
 	k8s.io/api v0.29.3
@@ -134,7 +134,7 @@
 	github.com/rubenv/sql-migrate v1.6.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
-	github.com/shopspring/decimal v1.3.1 // indirect
+	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/skeema/knownhosts v1.2.2 // indirect
 	github.com/spf13/cast v1.6.0 // indirect
@@ -155,8 +155,8 @@
 	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/oauth2 v0.18.0 // indirect
 	golang.org/x/sync v0.6.0 // indirect
-	golang.org/x/sys v0.18.0 // indirect
-	golang.org/x/term v0.18.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/term v0.19.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.19.0 // indirect
diff --git a/core/installer/go.sum b/core/installer/go.sum
index 49b5466..097b1ca 100644
--- a/core/installer/go.sum
+++ b/core/installer/go.sum
@@ -370,8 +370,8 @@
 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
-github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -444,8 +444,8 @@
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
 golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -499,15 +499,15 @@
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/core/installer/values-tmpl/launcher.cue b/core/installer/values-tmpl/launcher.cue
new file mode 100644
index 0000000..39328ba
--- /dev/null
+++ b/core/installer/values-tmpl/launcher.cue
@@ -0,0 +1,64 @@
+input: {
+	authGroups: string
+}
+
+_subdomain: "launcher"
+_domain: "\(_subdomain).\(global.privateDomain)"
+
+name: "launcher"
+namespace: "core-installer-welcome-launcher"
+readme: "App Launcher application will be installed on Private or Public network and be accessible at https://\(_domain)"
+description: "The application is a App launcher, designed to run all accessible applications. Can be configured to be reachable only from private network or publicly."
+icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 24 24'><path fill='currentColor' d='M15.43 15.48c-1.1-.49-2.26-.73-3.43-.73c-1.18 0-2.33.25-3.43.73c-.23.1-.4.29-.49.52h7.85a.978.978 0 0 0-.5-.52m-2.49-6.69C12.86 8.33 12.47 8 12 8s-.86.33-.94.79l-.2 1.21h2.28z' opacity='0.3'/><path fill='currentColor' d='M10.27 12h3.46a1.5 1.5 0 0 0 1.48-1.75l-.3-1.79a2.951 2.951 0 0 0-5.82.01l-.3 1.79c-.15.91.55 1.74 1.48 1.74m.79-3.21c.08-.46.47-.79.94-.79s.86.33.94.79l.2 1.21h-2.28zm-9.4 2.32c-.13.26-.18.57-.1.88c.16.69.76 1.03 1.53 1h1.95c.83 0 1.51-.58 1.51-1.29c0-.14-.03-.27-.07-.4c-.01-.03-.01-.05.01-.08c.09-.16.14-.34.14-.53c0-.31-.14-.6-.36-.82c-.03-.03-.03-.06-.02-.1c.07-.2.07-.43.01-.65a1.12 1.12 0 0 0-.99-.74a.09.09 0 0 1-.07-.03C5.03 8.14 4.72 8 4.37 8c-.3 0-.57.1-.75.26c-.03.03-.06.03-.09.02a1.24 1.24 0 0 0-1.7 1.03c0 .02-.01.04-.03.06c-.29.26-.46.65-.41 1.05c.03.22.12.43.25.6c.03.02.03.06.02.09m14.58 2.54c-1.17-.52-2.61-.9-4.24-.9c-1.63 0-3.07.39-4.24.9A2.988 2.988 0 0 0 6 16.39V18h12v-1.61c0-1.18-.68-2.26-1.76-2.74M8.07 16a.96.96 0 0 1 .49-.52c1.1-.49 2.26-.73 3.43-.73c1.18 0 2.33.25 3.43.73c.23.1.4.29.49.52zm-6.85-1.42A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29c-.37-.06-.74-.1-1.13-.1c-.99 0-1.93.21-2.78.58m21.56 0A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1c.4.68.63 1.46.63 2.29V18H24v-1.57c0-.81-.48-1.53-1.22-1.85M22 11v-.5c0-1.1-.9-2-2-2h-2c-.42 0-.65.48-.39.81l.7.63c-.19.31-.31.67-.31 1.06c0 1.1.9 2 2 2s2-.9 2-2'/></svg>"
+
+_httpPortName: "http"
+
+ingress: {
+	launcher: {
+		auth: {
+			enabled: true
+			groups: input.authGroups
+		}
+		network: networks.private
+		subdomain: _subdomain
+		service: {
+			name: "launcher"
+			port: name: _httpPortName
+		}
+	}
+}
+
+images: {
+    launcher: {
+        repository: "giolekva"
+        name: "launcher"
+        tag: "latest"
+        pullPolicy: "Always"
+    }
+}
+
+charts: {
+    launcher: {
+        chart: "charts/launcher"
+        sourceRef: {
+            kind: "GitRepository"
+            name: "pcloud"
+            namespace: global.id
+        }
+    }
+}
+
+helm: {
+    launcher: {
+        chart: charts.launcher
+        values: {
+            image: {
+                repository: images.launcher.fullName
+                tag: images.launcher.tag
+                pullPolicy: images.launcher.pullPolicy
+            }
+            portName: _httpPortName
+            logoutUrl: "https://accounts-ui.\(global.domain)/logout"
+        }
+    }
+}
diff --git a/core/installer/welcome/fake_app_directory.go b/core/installer/welcome/fake_app_directory.go
new file mode 100644
index 0000000..a8470d5
--- /dev/null
+++ b/core/installer/welcome/fake_app_directory.go
@@ -0,0 +1,112 @@
+package welcome
+
+type FakeAppDirectory struct {
+}
+
+func (s *FakeAppDirectory) GetAllApps() ([]AppLauncherInfo, error) {
+	// TODO(dtabidze): use repoIO to get actual info
+	googleInfo := AppLauncherInfo{
+		Name:        "Memberships",
+		Description: "Memberships Description",
+		Icon:        "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 24 24'><path fill='currentColor' d='M15.43 15.48c-1.1-.49-2.26-.73-3.43-.73c-1.18 0-2.33.25-3.43.73c-.23.1-.4.29-.49.52h7.85a.978.978 0 0 0-.5-.52m-2.49-6.69C12.86 8.33 12.47 8 12 8s-.86.33-.94.79l-.2 1.21h2.28z' opacity='0.3'/><path fill='currentColor' d='M10.27 12h3.46a1.5 1.5 0 0 0 1.48-1.75l-.3-1.79a2.951 2.951 0 0 0-5.82.01l-.3 1.79c-.15.91.55 1.74 1.48 1.74m.79-3.21c.08-.46.47-.79.94-.79s.86.33.94.79l.2 1.21h-2.28zm-9.4 2.32c-.13.26-.18.57-.1.88c.16.69.76 1.03 1.53 1h1.95c.83 0 1.51-.58 1.51-1.29c0-.14-.03-.27-.07-.4c-.01-.03-.01-.05.01-.08c.09-.16.14-.34.14-.53c0-.31-.14-.6-.36-.82c-.03-.03-.03-.06-.02-.1c.07-.2.07-.43.01-.65a1.12 1.12 0 0 0-.99-.74a.09.09 0 0 1-.07-.03C5.03 8.14 4.72 8 4.37 8c-.3 0-.57.1-.75.26c-.03.03-.06.03-.09.02a1.24 1.24 0 0 0-1.7 1.03c0 .02-.01.04-.03.06c-.29.26-.46.65-.41 1.05c.03.22.12.43.25.6c.03.02.03.06.02.09m14.58 2.54c-1.17-.52-2.61-.9-4.24-.9c-1.63 0-3.07.39-4.24.9A2.988 2.988 0 0 0 6 16.39V18h12v-1.61c0-1.18-.68-2.26-1.76-2.74M8.07 16a.96.96 0 0 1 .49-.52c1.1-.49 2.26-.73 3.43-.73c1.18 0 2.33.25 3.43.73c.23.1.4.29.49.52zm-6.85-1.42A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29c-.37-.06-.74-.1-1.13-.1c-.99 0-1.93.21-2.78.58m21.56 0A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1c.4.68.63 1.46.63 2.29V18H24v-1.57c0-.81-.48-1.53-1.22-1.85M22 11v-.5c0-1.1-.9-2-2-2h-2c-.42 0-.65.48-.39.81l.7.63c-.19.31-.31.67-.31 1.06c0 1.1.9 2 2 2s2-.9 2-2'/></svg>",
+		Help: []HelpDocument{
+			{
+				Title: "Memberships search",
+				Contents: `In the realm of coding, every keystroke is a brushstroke on the canvas of innovation. It's a domain where strings of characters dance to the rhythm of logic, constructing intricate digital symphonies. From the crisp lines of Python to the curly braces of JavaScript, each language tells a unique story, weaving together the fabric of our digital world.
+
+				In this labyrinth of code, programmers are modern-day alchemists, transforming abstract ideas into tangible realities. They navigate through the maze of syntax, debugging their creations with the precision of a surgeon. Errors are not failures but rather stepping stones on the path to mastery, each one offering a lesson in resilience and problem-solving.
+				
+				In the realm of algorithms, complexity is the name of the game. Programmers orchestrate elegant solutions to complex problems, employing a myriad of data structures and algorithms to optimize performance and efficiency. From sorting algorithms that arrange data in perfect harmony to search algorithms that uncover hidden treasures within vast datasets, the possibilities are as boundless as the imagination itself.
+				
+				But coding is not just about logic and structure; it's also a form of expression. Just as a poet wields words to evoke emotion, a coder crafts lines of code to breathe life into their creations. Whether it's a sleek user interface or a powerful backend system, every project is an opportunity to leave a mark on the digital landscape.
+				
+				In the collaborative ecosystem of coding, communities flourish and ideas thrive. Open-source repositories serve as the collective knowledge base of humanity, where programmers from around the globe collaborate to build upon each other's work. It's a testament to the power of collective intelligence, where the sum is truly greater than its parts.
+				
+				As we journey deeper into the digital age, the importance of coding becomes increasingly evident. It's not just a technical skill but a language of the future, a gateway to unlocking the full potential of technology. So let us embrace the beauty of code, for in its intricate tapestry lies the blueprint for tomorrow's innovations.`,
+				Children: []HelpDocument{
+					{
+						Title:    "Nested Child 1",
+						Contents: "Nested Child 1 Content",
+						Children: nil,
+					},
+					{
+						Title:    "Nested Child 2",
+						Contents: "Nested Child 2 Content",
+						Children: []HelpDocument{
+							{
+								Title:    "Nested Child 2.1",
+								Contents: "Nested Child 2.1 Content",
+								Children: []HelpDocument{
+									{
+										Title:    "Nested Child 2.1.1",
+										Contents: "Nested Child 2.1.1 Content",
+										Children: nil,
+									},
+									{
+										Title:    "Nested Child 2.1.2",
+										Contents: "Nested Child 2.1.2 Content",
+										Children: nil,
+									},
+								},
+							},
+							{
+								Title:    "Nested Child 2.2",
+								Contents: "Nested Child 2.2 Content",
+								Children: nil,
+							},
+						},
+					},
+					{
+						Title:    "Nested Child 3",
+						Contents: "Nested Child 3 Content",
+						Children: nil,
+					},
+				},
+			},
+			{
+				Title:    "Another Memberships search",
+				Contents: "Another Memberships Content",
+				Children: []HelpDocument{
+					{
+						Title:    "Another Nested Child 1",
+						Contents: "Another Nested Child 1 Content",
+						Children: nil,
+					},
+					{
+						Title:    "Another Nested Child 2",
+						Contents: "Another Nested Child 2 Content",
+						Children: nil,
+					},
+				},
+			},
+		},
+		Url: "https://memberships.p.v1.dodo.cloud",
+	}
+	gitInfo := AppLauncherInfo{
+		Name:        "Gerrit",
+		Description: "Gerrit Description",
+		Icon:        "<svg fill='#000000' width='50px' height='50px' viewBox='0 0 24 24' role='img' xmlns='http://www.w3.org/2000/svg'><g id='SVGRepo_bgCarrier' stroke-width='0'></g><g id='SVGRepo_tracerCarrier' stroke-linecap='round' stroke-linejoin='round'></g><g id='SVGRepo_iconCarrier'><title>Gerrit icon</title><path d='M12.648 2.678l-.245-.266c.004-.004.29-.268.413-.41.121-.146.342-.484.346-.486l.301.195c-.014.016-.234.359-.375.522-.137.165-.428.432-.44.445zm1.577 10.597c-.012-.004-.24-.154-.365-.221-.117-.059-.32-.146-.422-.191l.213-.612-.898-.444-.286.871c-.174.004-.713.053-1.51.389-.959.4-1.688 1.025-1.695 1.029l-.143.125.641.025.02-.016c.006-.006.721-.535 1.119-.705.088-.037.207-.074.33-.105-.209.105-.439.227-.6.32-.199.119-.57.381-.586.393l-.186.129.682.016.018-.01c.012-.008 1.164-.623 1.789-.76l.196-.047c.145-.037.246-.064.422-.064.156 0 .369.021.688.07.398.059.66.158.664.16l.24.094-.322-.436-.012-.008.003-.002zm-8.98-7.298h-.029l-1.006.916v.03c-.016.266.078.52.258.716.182.196.42.309.686.319h.043c.531 0 .965-.413.992-.94.023-.545-.399-1.01-.944-1.041zM5.2 7.808h-.041c-.117-.005-.23-.032-.33-.085.045.008.098.01.148.002.284-.035.481-.291.448-.575-.035-.279-.293-.48-.576-.442-.264.034-.457.267-.446.531-.031-.09-.045-.183-.045-.28l.912-.833c.446.042.79.431.768.882s-.391.8-.842.8H5.2zm9.329-3.725l-.293-.195-.41.265-.395-.285-.301.18.404.291-.416.27.297.18.4-.254.387.28.309-.169-.407-.296M24 19.525c-.213-.209-.418-.416-.629-.627-.48-.488-.957-.984-1.418-1.486-.547-.598-1.082-1.207-1.582-1.844-.225-.283-.441-.58-.646-.881-.254-.387-.469-.795-.668-1.215-.211-.445-.398-.9-.576-1.356.24.21.463.444.664.692.021-.215.041-.43.059-.648l.023-.322c0-.033.012-.066-.008-.096-.016-.029-.033-.051-.053-.075-.121-.149-.264-.282-.406-.413-.189-.181-.387-.36-.584-.533l-.18-.156c-.027-.027-.057-.051-.086-.074-.031-.03-.041-.096-.055-.136l-.154-.479c.607.285 1.109.741 1.578 1.215.004-.219.004-.436 0-.652 0-.114-.002-.229-.008-.346 0-.029.006-.1-.016-.127-.012-.02-.031-.036-.043-.055-.109-.117-.234-.217-.357-.314-.172-.143-.35-.277-.527-.408-.156-.117-.318-.232-.477-.345-.018-.016-.076-.039-.064-.06l.039-.08c.02-.035.014-.045.053-.029l.17.059c.406.156.779.368 1.143.608-.123-.45-.311-.885-.525-1.296-.389-.762-.893-1.468-1.463-2.105-.379-.426-.785-.824-1.219-1.193-.223-.19-.457-.369-.699-.533-.113-.074-.225-.149-.346-.217-.049-.03-.105-.056-.15-.094-.18-.144-.365-.284-.549-.429.238.098.469.21.689.338-.127-.194-.342-.331-.545-.436-.307-.159-.637-.276-.967-.378.113-.12.234-.228.346-.348.113-.12.223-.246.33-.372.127-.15.252-.3.375-.455l-1.156-.726-.48-.303c-.123.246-.277.476-.458.68-.18.203-.391.369-.6.539-.191.156-.379.316-.566.472-.047.04-.092.085-.145.12-.025.021-.096.004-.127.004H10.7c-.297.006-.596.029-.891.068-.058.007-.113.013-.175.008l-.178-.01c-.137-.008-.271-.016-.408-.016-.289-.004-.58 0-.871.025-.244.022-.489.055-.729.112-.238.056-.461.172-.66.312-.193.141-.387.32-.504.53s-.17.456-.213.689c-.135.013-.268.01-.4.016-.141.008-.277.021-.416.039-.27.037-.535.096-.795.18-.496.154-.945.405-1.336.75-.195.181-.377.38-.539.596-.02.025-.037.051-.063.068-.01.009-.027.016-.033.027-.015.027-.033.052-.051.076l-.134.212c-.168.285-.276.595-.383.906l-.045.123.033-.021c-.01.067-.02.135-.025.202l-.008.105v.053l-.066.013c-.135.026-.271.06-.4.101-.12.039-.233.086-.337.15-.105.066-.195.153-.285.239-.318.31-.562.698-.687 1.128-.1.348-.137.735-.059 1.092.021.09.049.188.107.262.074.09.199.126.313.095.131-.036.248-.124.371-.187l.314-.157c.455-.226.93-.446 1.438-.525.035-.005.1.067.131.091.049.037.1.074.148.104.109.069.221.129.334.181.262.114.541.174.818.231.502.105 1.014.171 1.529.18.207.004.416 0 .625-.018.225-.02.451-.049.678-.051.25-.005.496.025.74.055.254.031.51.068.764.105.518.079 1.031.169 1.543.279-.115.18-.227.362-.338.545-.008.013-.072 0-.088 0-.041-.002-.086-.002-.127 0-.074 0-.15.008-.225.019-.211.03-.416.083-.615.156-.49.181-.938.483-1.326.833-.221.195-.43.408-.609.641-.049.064-.094.129-.139.193.105-.023.211-.045.318-.07.061-.016.117-.027.176-.039.021-.008.029-.016.049-.027.219-.203.451-.393.695-.563.111-.08.225-.154.348-.215.166-.083.354-.131.533-.174-.33.18-.678.35-.977.584-.172.139-.328.291-.49.439l.654-.104c.01 0 .014-.004.023-.01l.17-.094c.113-.064.23-.125.346-.186.24-.123.482-.24.732-.34.236-.094.48-.178.732-.225.221-.046.428-.052.648 0 .342.074.67.232.969.414.021.014.035.033.055.014.016-.014.098-.074.09-.092l-.18-.334c-.016-.029-.023-.049-.051-.066l-.131-.077c-.176-.104-.348-.21-.51-.331-.037-.023-.078-.039-.082-.089-.004-.046.014-.093.029-.136.033-.09.08-.169.131-.248.033-.058.072-.111.109-.166.014-.018.01-.021.031-.014l.119.034c.236.077.469.159.711.22.27.068.545.123.82.176l.048.01c-.056-.03-.091-.09-.132-.136-.023-.03-.035-.05-.074-.06l-.105-.02c-.074-.016-.148-.03-.225-.049-.262-.057-.525-.119-.777-.209-.375-.13-.758-.232-1.145-.322-.486-.107-.977-.194-1.465-.275-.25-.041-.498-.074-.75-.109-.246-.034-.496-.07-.746-.087-.539-.034-1.074.087-1.615.081-.365-.004-.734-.055-1.096-.105l-.068-.009c.252-.093.5-.188.75-.285.236-.095.471-.192.705-.289.127-.052.25-.105.373-.157.113-.051.225-.1.328-.17.408-.279.676-.727.975-1.109.273-.361.586-.7.99-.908.129-.066.262-.117.4-.156-.283-.439-.549-.93-.623-1.455.029.012.061.025.094.037.035.012.064.016.072.045l.029.135c.026.091.053.176.086.262.072.191.16.375.26.551.219.396.484.766.766 1.12.539.686 1.145 1.305 1.736 1.941.145.158.275.324.41.492.15.187.303.375.457.561.121.141.234.285.356.426l.094.112c.016.018.037.026.061.036.604.3 1.213.6 1.822.895.434.209.869.42 1.309.623.236.109.471.219.711.32.029.014.066.029.098.039.016.01.029.016.047.023l.016.049c.027.072.051.145.076.217.049.141.098.279.148.416.33.912.695 1.814 1.145 2.676.346.656.715 1.301 1.09 1.939.4.68.811 1.354 1.225 2.025.332.535.666 1.072 1.008 1.605.146.227.289.459.439.688l.063.094.775-1.141.191-.283c.008-.006.082-.102.076-.109l-.156-.24c-.291-.451-.584-.898-.871-1.35l-.828-1.283-.105-.166.656.799 1.115 1.35c.121.146.236.289.354.438.219-.402.439-.801.662-1.201.086-.164.176-.33.266-.492M12.13 1.915c.309-.246.563-.563.757-.906l1.178.743.18.112c-.246.3-.496.604-.775.873-.035.034-.074.069-.107.104-.021.025-.047.051-.07.075l-.033.038c-.023-.007-.045-.016-.068-.02-.146-.041-.289-.08-.436-.111-.292-.066-.589-.117-.886-.152-.191-.021-.385-.04-.578-.058.275-.234.557-.467.838-.698m-1.754 1.37c.232-.096.482-.15.73-.191.49-.082.994-.094 1.489-.04.354.038.701.108 1.041.21l-.141.115-.225-.061c-.141-.029-.281-.06-.422-.082-.246-.037-.497-.06-.749-.065-.461-.008-.926.042-1.371.159-.301.078-.604.188-.879.332-.286.154-.555.346-.78.582-.134.137-.257.289-.359.455-.057.09-.105.18-.15.275-.024.047-.043.096-.061.144l-.029.075c-.004.015-.008.026-.012.038L8.39 5.22l-.082-.011c.234-.719.763-1.286 1.418-1.649.207-.114.426-.218.65-.279m-3.93.374c.266-.381.686-.649 1.139-.743.502-.101 1.016-.119 1.525-.107-.525.131-1.05.326-1.488.652-.34.075-.664.24-.934.459-.09.071-.17.15-.246.236-.035.037-.066.075-.097.116-.014.015-.027.03-.037.049h-.084c.023-.135.055-.27.098-.4.029-.09.064-.18.119-.26m.882.056c-.145.143-.27.303-.391.469-.047.069-.098.153-.18.191-.085.039-.194-.005-.28-.031.224-.279.521-.494.851-.629M2.308 6.585c.08-.16.182-.315.275-.465.016-.027.053-.046.074-.063.049-.034.096-.069.143-.106.309-.225.621-.446.939-.656.166-.107.33-.213.502-.307.182-.101.369-.18.565-.244.347-.112.707-.189 1.068-.244.174-.025.361-.057.531-.015-.33.188-.658.375-.99.558-.342.191-.689.367-1.035.552-.332.18-.66.375-.981.577-.336.206-.67.419-1.002.629-.08.051-.16.105-.24.155.045-.125.09-.251.151-.371m1.009 3.056c-.029.056-.134.042-.187.042-.094 0-.186 0-.279.006-.258.015-.51.06-.758.138-.475.147-.922.375-1.365.604-.1.055-.203.135-.316.162-.061.015-.131-.008-.166-.063-.035-.061-.053-.131-.064-.198-.027-.12-.035-.245-.033-.368 0-.33.076-.66.215-.96.096-.21.221-.401.371-.574.15-.175.326-.351.539-.45.355-.162.775-.213 1.164-.235.072-.005.145-.007.217-.007.1 0 .217-.016.311.019.074.027.121.105.15.173.045.105.074.225.1.336.061.26.123.525.145.791.01.119.016.239.004.359-.009.073-.014.156-.05.223m2.829-1.973c-.194.123-.403.218-.616.298-.215.077-.436.144-.66.181-.097.013-.195.025-.293.021-.101-.003-.193-.036-.293-.067-.195-.063-.393-.13-.57-.233-.064-.039-.135-.084-.174-.15-.029-.052-.029-.116-.023-.174.012-.231.094-.458.203-.66.187-.343.482-.612.84-.775.879-.396 1.865-.029 2.611.49l.08.061c-.154.165-.311.321-.473.477-.199.189-.404.381-.634.531M8.31 5.739c-.475-.203-.926-.458-1.356-.738.449.188.934.3 1.414.37.258.037.521.072.781.08.281.008.564-.021.84-.075.545-.103 1.068-.305 1.566-.551.494-.245.964-.537 1.413-.859.217-.155.43-.315.633-.487.021-.016.174-.161.184-.154l.041.031.537.416c.328.254.658.51.988.762-.906.326-1.826.629-2.752.904-.519.156-1.038.301-1.565.42-.412.098-.834.189-1.256.21-.507.022-1.006-.135-1.47-.33m8.85 3.942c.076.021.145.045.215.067l.094.033c.016.006.031.015.045.02l.021.06c.045.146.09.289.139.432-.15-.127-.301-.254-.451-.379l-.09-.074c-.021-.016-.045-.021-.029-.046l.059-.114m-.671 1.444l.035-.063.027-.046c.012-.018.008-.022.029-.012.129.054.258.111.385.17.24.11.475.23.703.364.107.065.217.135.322.205l.15.105.074.057c.033.027.041.063.057.102.104.282.219.564.338.844.078.189.162.379.248.566-.293-.371-.621-.715-.957-1.045-.346-.346-.705-.671-1.078-.981l-.323-.264m3.746 6.42l-.121.09.008.016.063.094.271.42.904 1.402c.311.48.621.963.932 1.445l.309.48.084.133c.004.004.029.041.029.045-.26.385-.523.77-.783 1.154-.027.037-.051.076-.074.111-.24-.373-.479-.744-.715-1.117-.4-.635-.795-1.277-1.184-1.916-.434-.709-.855-1.418-1.264-2.141-.383-.674-.75-1.361-1.059-2.076-.301-.697-.563-1.408-.811-2.121.486.193.98.367 1.48.521.145.045.289.09.436.127l.063.018c.008 0 .014-.039.018-.049.018-.064.031-.129.045-.195.031-.125.051-.254.074-.381.205.428.436.844.701 1.236.215.314.445.621.686.92.521.656 1.074 1.283 1.643 1.898.463.494.934.984 1.408 1.465l.389.389c.006.006.039.031.037.041l-.031.053-.148.275-.588 1.068c-.18-.219-.361-.436-.541-.658l-1.125-1.361c-.314-.387-.637-.773-.953-1.16l-.186-.225'></path></g></svg>",
+		Help: []HelpDocument{
+			{
+				Title:    "Gerrit Doc",
+				Contents: "Some random Gerrit help.",
+				Children: nil,
+			},
+		},
+		Url: "https://code.v1.dodo.cloud/dashboard/self",
+	}
+	forumge := AppLauncherInfo{
+		Name:        "Forum",
+		Description: "Forum Description",
+		Icon:        "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 256 256'><path fill='currentColor' d='M100 140a8 8 0 1 1-8-8a8 8 0 0 1 8 8Zm64 8a8 8 0 1 0-8-8a8 8 0 0 0 8 8Zm64.94-9.11a12.12 12.12 0 0 1-5 1.11a11.83 11.83 0 0 1-9.35-4.62l-2.59-3.29V184a36 36 0 0 1-36 36H80a36 36 0 0 1-36-36v-51.91l-2.53 3.27A11.88 11.88 0 0 1 32.1 140a12.08 12.08 0 0 1-5-1.11a11.82 11.82 0 0 1-6.84-13.14l16.42-88a12 12 0 0 1 14.7-9.43h.16L104.58 44h46.84l53.08-15.6h.16a12 12 0 0 1 14.7 9.43l16.42 88a11.81 11.81 0 0 1-6.84 13.06ZM97.25 50.18L49.34 36.1a4.18 4.18 0 0 0-.92-.1a4 4 0 0 0-3.92 3.26l-16.42 88a4 4 0 0 0 7.08 3.22ZM204 121.75L150 52h-44l-54 69.75V184a28 28 0 0 0 28 28h44v-18.34l-14.83-14.83a4 4 0 0 1 5.66-5.66L128 186.34l13.17-13.17a4 4 0 0 1 5.66 5.66L132 193.66V212h44a28 28 0 0 0 28-28Zm23.92 5.48l-16.42-88a4 4 0 0 0-4.84-3.16l-47.91 14.11l62.11 80.28a4 4 0 0 0 7.06-3.23Z'/></svg>",
+		Help: []HelpDocument{
+			{
+				Title:    "Forum Doc",
+				Contents: "Some random Forum help.",
+				Children: nil,
+			},
+		},
+		Url: "https://forum.ge",
+	}
+	return []AppLauncherInfo{googleInfo, gitInfo, forumge}, nil
+}
diff --git a/core/installer/welcome/launcher-tmpl/launcher.html b/core/installer/welcome/launcher-tmpl/launcher.html
new file mode 100644
index 0000000..3511064
--- /dev/null
+++ b/core/installer/welcome/launcher-tmpl/launcher.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en" data-theme="light">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>App Launcher</title>
+    <link rel="stylesheet" type="text/css" href="/static/pico.2.0.6.min.css">
+    <link rel="stylesheet" type="text/css" href="/static/launcher.css">
+</head>
+<body class="container-fluid">
+    <div id="left-panel">
+        <div class="user-circle">
+            <div class="circle">
+                <p>{{ GetUserInitials .LoggedInUsername }}</p>
+                <div class="tooltip-user" id="tooltip-user">
+                    <p>{{ .LoggedInUsername }}</p>
+                    <button id="logout-button">Log out</button>
+                </div>
+            </div>
+        </div>
+        <hr class="separator">
+        <div class="app-list">
+            {{range .AllAppsInfo}}
+                <div class="app-icon-tooltip" data-app-url="{{ .Url }}">
+                    <div class="icon">
+                        {{.Icon}}
+                    </div>
+                    <div class="tooltip">
+                        <p>{{ .Name }}</p>
+                        <button class="help-button" id="help-button-{{ CleanAppName .Name }}">Help</button>
+                    </div>
+                </div>
+                <dialog class="app-help-modal" id="modal-{{ CleanAppName .Name }}" close>
+                    <article class="modal-article">
+                        <header>
+                            <h4>{{ .Name }}</h4>
+                            <button class="close-button" id="close-help-{{ CleanAppName .Name }}">
+                                <svg xmlns="http://www.w3.org/2000/svg" width="1.5em" height="1.5em" viewBox="0 0 32 32"><path fill="black" d="M16 2C8.2 2 2 8.2 2 16s6.2 14 14 14s14-6.2 14-14S23.8 2 16 2m5.4 21L16 17.6L10.6 23L9 21.4l5.4-5.4L9 10.6L10.6 9l5.4 5.4L21.4 9l1.6 1.6l-5.4 5.4l5.4 5.4z"/></svg>
+                            </button>
+                        </header>
+                        <div class="app-help-modal-article">
+                            <div class="modal-left">
+                                {{ template "help-menu-template" .Help }}
+                            </div>
+                            <div class="modal-right" id="modal-right-help-content-{{ CleanAppName .Name }}">
+                                {{ template "help-content-template" .Help }}
+                            </div>
+                        </div>
+                    </article>
+                </dialog>
+            {{end}}
+        </div>
+    </div>
+    <div id="right-panel">
+        <iframe id="appFrame" width="100%" height="100%" frameborder="0"></iframe>
+    </div>
+    {{ define "help-menu-template" }}
+        <ul class="ul">
+            {{ range .}}
+                <li>
+                    <a class="title-menu" id="title-{{ CleanAppName .Title }}">{{ .Title }}</a>
+                    {{ template "help-menu-template" .Children }}
+                </li>
+            {{ end }}
+        </ul>
+    {{ end }}
+    {{ define "help-content-template" }}
+        {{ range . }}
+            <p class="help-content" id="help-content-{{ CleanAppName .Title }}"> {{ .Contents }}</p>
+            {{ template "help-content-template" .Children }}
+        {{ end }}
+    {{ end }}
+    <script src="/static/launcher.js"></script>
+</body>
+</html>
diff --git a/core/installer/welcome/launcher.go b/core/installer/welcome/launcher.go
new file mode 100644
index 0000000..6f40714
--- /dev/null
+++ b/core/installer/welcome/launcher.go
@@ -0,0 +1,107 @@
+package welcome
+
+import (
+	"embed"
+	"fmt"
+	"html/template"
+	"log"
+	"net/http"
+	"strings"
+)
+
+//go:embed launcher-tmpl/launcher.html
+var indexHTML embed.FS
+
+//go:embed static/*
+var files embed.FS
+
+type AppLauncherInfo struct {
+	Name        string
+	Description string
+	Icon        template.HTML
+	Help        []HelpDocument
+	Url         string
+}
+
+type HelpDocument struct {
+	Title    string
+	Contents string
+	Children []HelpDocument
+}
+
+type AppDirectory interface {
+	GetAllApps() ([]AppLauncherInfo, error)
+}
+
+type LauncherServer struct {
+	port         int
+	logoutUrl    string
+	appDirectory AppDirectory
+	homeTmpl     *template.Template
+}
+
+func NewLauncherServer(
+	port int,
+	logoutUrl string,
+	appDirectory AppDirectory,
+) (*LauncherServer, error) {
+	tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse template: %v", err)
+	}
+	t := template.New("index").Funcs(template.FuncMap{
+		"GetUserInitials": getUserInitials,
+		"CleanAppName":    cleanAppName,
+	})
+	t, err = t.Parse(string(tmpl))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse template: %v", err)
+	}
+	return &LauncherServer{
+		port,
+		logoutUrl,
+		appDirectory,
+		t,
+	}, nil
+}
+
+func getUserInitials(username string) string {
+	if username == "" {
+		return ""
+	}
+	return strings.ToUpper(username[:1])
+}
+
+func cleanAppName(name string) string {
+	cleanName := strings.ToLower(name)
+	cleanName = strings.ReplaceAll(cleanName, " ", "-")
+	return cleanName
+}
+
+func (s *LauncherServer) Start() {
+	http.Handle("/static/", http.FileServer(http.FS(files)))
+	http.HandleFunc("/", s.homeHandler)
+	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
+}
+
+type homeHandlerData struct {
+	LoggedInUsername string
+	AllAppsInfo      []AppLauncherInfo
+}
+
+func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
+	loggedInUsername := "longusername"
+	allAppsInfo, err := s.appDirectory.GetAllApps()
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+	data := homeHandlerData{
+		LoggedInUsername: loggedInUsername,
+		AllAppsInfo:      allAppsInfo,
+	}
+	if err := s.homeTmpl.Execute(w, data); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
diff --git a/core/installer/welcome/static/launcher.css b/core/installer/welcome/static/launcher.css
new file mode 100644
index 0000000..6802bf2
--- /dev/null
+++ b/core/installer/welcome/static/launcher.css
@@ -0,0 +1,290 @@
+:root:not([data-theme]) {
+    --pico-background-color: unset;
+    --pico-color: unset;
+}
+
+body {
+    margin: 0;
+    padding: 0;
+    font-family: Arial, sans-serif;
+    display: flex;
+    height: 100vh;
+    padding-left: 10px !important;
+    padding-right: 10px !important;
+    background-color: black;
+}
+
+#left-panel {
+    width: 80px;
+    background-color: #f0f0f0;
+    border-radius: 10px;
+    border-width: 1px;
+    border-color: black;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin-top: 5px;
+    margin-bottom: 5px;
+}
+
+.app-list {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+#right-panel {
+    flex: 1;
+    background-color: #f0f0f0;
+    margin: 5px;
+    padding: 2px;
+    border-radius: 10px;
+    border-color: black;
+}
+
+#appFrame {
+    border-radius: 10px;
+}
+
+.app-icon-tooltip {
+    position: relative;
+    display: inline-block;
+    align-items: flex-start;
+    justify-content: center;
+    cursor: initial;
+    width: 80px !important;
+    height: 50px !important;
+    margin-bottom: 10px !important;
+    cursor: pointer !important;
+    --pico-background-color: unset !important;
+    --pico-color: unset !important;
+}
+
+.tooltip {
+    position: absolute;
+    width: 200px;
+    border-radius: 0 10px 10px 0;
+    top: 70%;
+    left: 98%;
+    transform: translateY(-50%);
+    background-color: black;
+    color: white;
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+    padding: 5px;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    visibility: hidden;
+    opacity: 0;
+    cursor: auto;
+    font-size: 16px;
+}
+
+.help-button {
+    margin-top: 5px !important;
+    padding: 0 !important;
+    border: 0 !important;
+    margin-bottom: 5px !important;
+    width: 100% !important;
+    color: white !important;
+    cursor: pointer !important;
+    font-size: 16px !important;
+}
+
+.icon {
+    display: flex;
+    justify-content: center;
+    align-items: center !important;
+}
+
+.tooltip p {
+    color: white;
+    margin: 0;
+    cursor: auto;
+}
+
+.app-icon-tooltip:hover {
+    transform: scale(1.15);
+}
+
+.app-icon-tooltip .background-glow {
+    position: absolute;
+    top: 0;
+    left: 4px;
+    right: 4px;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0);
+    pointer-events: none;
+    border-radius: 5px;
+    box-shadow: 0px 0px 7px 7px black;
+}
+
+.modal-left {
+    width: 30%;
+    overflow-y: auto;
+    float: left;
+    margin-left: 0px;
+    background-color: #fbfcfc;
+    border-radius: 2px;
+}
+
+.modal-right {
+    /* flex: 1; */
+    width: 70%;
+    overflow-y: auto;
+    float: right;
+    margin-left: 2px;
+}
+
+.app-help-modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    overflow: hidden;
+}
+
+.app-help-modal-article {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+    max-width: 100%;
+    min-height: 97%;
+    max-height: 97%;
+    overflow: hidden;
+}
+
+.app-info-modal-article header {
+    flex: 0 0 auto;
+}
+
+header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    position: relative;
+    margin-bottom: 2px !important;
+}
+
+.close-button {
+    padding: 0;
+    border: none;
+    background: none;
+    cursor: pointer;
+    outline: none;
+    width: 1.5em;
+    height: 1.5em;
+    position: absolute;
+    top: 11px;
+    right: 5px;
+}
+
+.modal-article {
+    min-width: 80% !important;
+    max-width: 80% !important;
+    min-height: 90% !important;
+    max-height: 90% !important;
+    overflow: hidden;
+}
+
+.help-content {
+    display: none;
+}
+
+.circle {
+    width: 50px;
+    height: 50px;
+    border-radius: 50%;
+    background-color: #ccc;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.circle p {
+    font-size: 24px;
+    text-align: center;
+    line-height: 50px;
+    margin: 0;
+    position: relative;
+    display: inline-block;
+}
+
+.user-circle {
+    min-width: 80px !important;
+    max-width: 80px !important;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.separator {
+    margin-top: 2px !important;
+    margin-bottom: 4px !important;
+    border-width: 2px !important;
+    border-color: black !important;
+    width: 100% !important;
+}
+
+ul {
+    padding-inline-start: 0px !important;
+    margin-bottom: 0px;
+    list-style: none;
+    font-size: 14px;
+}
+
+ul ul {
+    padding-inline-start: 19px;
+}
+
+ul li {
+    list-style: none !important;
+    padding-inline-start: 19px !important;
+    margin-bottom: 0px;
+    font-size: 16px !important;
+}
+
+.tooltip-user {
+    position: absolute;
+    top: 54px;
+    left: 90px;
+    transform: translateY(-50%);
+    width: 234px;
+    background-color: black;
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+    padding: 5px;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    visibility: hidden;
+    opacity: 0;
+    border-radius: 0 0 10px 0;
+    cursor: auto;
+}
+
+.tooltip-user button {
+    margin-top: 5px !important;
+    padding: 0 !important;
+    border: 0 !important;
+    margin-bottom: 5px !important;
+    width: 100% !important;
+    color: white !important;
+    cursor: pointer !important;
+    font-size: 19px !important;
+}
+
+.tooltip-user p {
+    color: white;
+    margin: 0;
+    cursor: auto;
+    font-size: 19px;
+    /* align-self: flex-start; */
+}
diff --git a/core/installer/welcome/static/launcher.js b/core/installer/welcome/static/launcher.js
new file mode 100644
index 0000000..b8c2ae5
--- /dev/null
+++ b/core/installer/welcome/static/launcher.js
@@ -0,0 +1,78 @@
+document.addEventListener("DOMContentLoaded", function () {
+    function showTooltip(obj) {
+        // obj.style.display = 'flex';
+        obj.style.visibility = 'visible';
+        obj.style.opacity = '1';
+    }
+    function hideTooltip(obj) {
+        obj.style.visibility = 'hidden';
+        obj.style.opacity = '0';
+        // obj.style.display = '';
+    }
+    const circle = document.querySelector(".user-circle");
+    const tooltipUser = document.querySelector("#tooltip-user");
+    [
+        ['mouseenter', () => showTooltip(tooltipUser)],
+        ['mouseleave', () => hideTooltip(tooltipUser)],
+    ].forEach(([event, listener]) => {
+        circle.addEventListener(event, listener);
+    });
+    const icons = document.querySelectorAll(".app-icon-tooltip");
+    icons.forEach(function (icon) {
+        icon.addEventListener("click", function (event) {
+            event.stopPropagation();
+            const appUrl = this.getAttribute("data-app-url");
+            document.getElementById('appFrame').src = 'about:blank';
+            document.getElementById('appFrame').src = appUrl;
+            document.querySelectorAll(".app-icon-tooltip .background-glow").forEach((e) => e.remove());
+            const glow = document.createElement('div');
+            glow.classList.add("background-glow");
+            glow.setAttribute("style", "transform: none; transform-origin: 50% 50% 0px;")
+            this.appendChild(glow);
+        });
+        const tooltip = icon.querySelector('.tooltip');
+        tooltip.addEventListener("click", function (event) {
+            event.stopPropagation();
+        });
+        [
+            ['mouseenter', () => showTooltip(tooltip)],
+            ['mouseleave', () => hideTooltip(tooltip)],
+            ['focus', () => showTooltip(tooltip)],
+            ['blur', () => hideTooltip(tooltip)],
+        ].forEach(([event, listener]) => {
+            icon.addEventListener(event, listener);
+        });
+    });
+    const helpButtons = document.querySelectorAll('.help-button');
+    helpButtons.forEach(function (button) {
+        button.addEventListener('click', function (event) {
+            event.stopPropagation();
+            const buttonId = button.getAttribute('id');
+            const modalId = 'modal-' + buttonId.substring("help-button-".length);
+            const closeHelpId = "close-help-" + buttonId.substring("help-button-".length);
+            const modal = document.getElementById(modalId);
+            modal.removeAttribute("close");
+            modal.setAttribute("open", true);
+            const closeHelpButton = document.getElementById(closeHelpId);
+            closeHelpButton.addEventListener('click', function (event) {
+                event.stopPropagation();
+                modal.removeAttribute("open");
+                modal.setAttribute("close", true);
+            });
+        });
+    });
+    const modalHelpButtons = document.querySelectorAll('.title-menu');
+    modalHelpButtons.forEach(function (button) {
+        button.addEventListener('click', function (event) {
+            event.stopPropagation();
+            const helpTitle = button.getAttribute('id');
+            const helpTitleId = helpTitle.substring('title-'.length);
+            const helpContentId = 'help-content-' + helpTitleId;
+            const allContentElements = document.querySelectorAll('.help-content');
+            allContentElements.forEach(function (contentElement) {
+                contentElement.style.display = 'none';
+            });
+            document.getElementById(helpContentId).style.display = 'block';
+        });
+    });
+});
diff --git a/docs/rfd/5_LAUNCHER.md b/docs/rfd/5_LAUNCHER.md
new file mode 100644
index 0000000..4a05571
--- /dev/null
+++ b/docs/rfd/5_LAUNCHER.md
@@ -0,0 +1,67 @@
+# DODO Homepage
+Outlines the layout and functionality of the PCloud homepage, specifying the arrangement and behavior of its panels and content inside it.
+
+## Background Information
+PCloud customers will want to have all of their applications in one place. This homepage will serve as a central hub for customers, allowing them to easily switch between apps. It's has indirect relationship with AppManager, user with corresponding permission will be able to install/uninstall apps.
+
+## Goals
+- This will be a main page for users.
+- Authenticated users should be able to search and run applications.
+- [Stretch] Monitor applcation health.
+- [Stretch] Documentation or some sort of small tutorial how to use application.
+
+## Non-Goals
+- Installing applications should not be a responsibility of the launcher, that is taken care of app manager. 
+- Uninstalling applications should not be in the initial implementation of the launcher.
+
+## Technichal overview
+The homepage will serve a single page at the root path `/`, dedicated to presenting authenticated users with a list of applications they have permission to use. Users will get assigned groups via the memberships API. For implementing web server the standard [net/http](https://pkg.go.dev/net/http) is sufficient. The server will feature only one handler:
+* `GET /` This handler will retrieve all the necessary details to render information of application and run it. This information includes the application's Name, Description, Documentation, Domain, Icon, Health status, and any additional relevant details. These details will be obtained from corresponding cue files of the applications.
+Storage component will have following interface:
+```go
+type AppLauncherInfo struct {
+	Name         string
+	Description  string
+	Icon         template.HTML
+	HelpDocument []HelpDocument
+	Url          string
+}
+
+type HelpDocument struct {
+	Title    string
+	Contents string
+	Children []HelpDocument
+}
+
+type AppDirectory interface {
+	GetAllAppsInfo() ([]AppLauncherInfo, error)
+}
+```
+* Application information can be retrived from Application Manager Service.
+- ### UI
+Single HTML template will be sufficient to render the home page via [html/template](https://pkg.go.dev/html/template) package. And [embed](https://pkg.go.dev/embed) package will be used to embed said HTML template into the final binary.
+
+- Divide the homepage into two independent panels, Left and Right:
+- Ensure non-scrollability of the entire page; any scrolling needed should be confined within specific panels.
+- Implement a minimization option for left panel.
+- Display authenticated user information at the top of left panel.
+   - Render user's icon/image, possible with the user's name. Enable interaction with the icon to display user info and possible editing options. This information could be rendered in a modal window.
+   - Include additional small user information (if necessary).
+   - When minimized, show only the user icon.
+- Under User icon will be search icon and a vertical navigation bar with a list of applications inside.
+  - The search icon should open a modal window with a search bar, allowing users to filter applications by name. Clicking on the search output should open the application in Right panel.
+  - Display app icons with names and optional additional information.
+    - Include Rendering app manager(app store) icon, and if someone wants to install new app, they can open app manager, search for app and install it.
+  - If left side panel is minimized, show only app icons.
+  - Implement Hover above App Icon functionality. It should show tooltip on right side of App icon. 
+    - Add Help button inside tooltip. Apon clicking on it, Documentation modal of application should show up.
+    - Create separate Documentation Modal for each application.
+    - Documentation modal must be divided into two, left and right panels.
+      - Left panel should be underodred list of Documentation titles and after clicking any title, corresponding content should be rendered in right panel.
+      - Documentation can be nested. We have to use recursion to get all Documentation titles and corresponding content. This can be done directly inside an HTML file using Go Templates.
+      - When DOM loads we create all documentation content tags, but they will be hidden.
+      - Documentation content will be displayed only when interacting with the corresponding Title. To achieve this, we must create event listeners. To differentiate which title should open which content, we need to generate unique IDs for every tag. The relationship between the Title tag ID and the corresponding content tag ID must be maintained. Specifically, the Title ID should be `title-generatedID`, and the corresponding content ID should be `help-content-generatedID`, where the generated ID for the corresponding pair remains the same. We can generate these IDs directly inside an HTML file using the library [Sprig](https://github.com/Masterminds/sprig).
+    - Documentation modal will have application name on left top position and close icon on top right corner.
+  - Enable app launch only for authenticated user by clicking on the respective icon.
+    - Each App Icon will have JS event listener, which will open coresponding application in Right panel in iframe tag.
+    - The icon of the currently running app can be highlighted with a green circle.