From 3ef0c93c8f312fb98ce2da52acfb8aa9503f9e39 Mon Sep 17 00:00:00 2001
From: Khairul Hidayat <me@khairul.my.id>
Date: Sat, 9 Nov 2024 11:47:57 +0000
Subject: [PATCH] feat: add incus user option

---
 frontend/pages/hosts/components/form.tsx | 15 ++++++++++++---
 frontend/pages/hosts/schema/form.ts      |  1 +
 server/app/ws/term_incus.go              |  6 +++++-
 server/lib/incus.go                      | 15 +++++++++++++--
 4 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/frontend/pages/hosts/components/form.tsx b/frontend/pages/hosts/components/form.tsx
index aca6272..61dba79 100644
--- a/frontend/pages/hosts/components/form.tsx
+++ b/frontend/pages/hosts/components/form.tsx
@@ -172,9 +172,18 @@ const IncusFormFields = ({ form }: MiscFormFieldProps) => {
         />
       </FormField>
       {type === "lxc" && (
-        <FormField label="Shell">
-          <InputField form={form} name="metadata.shell" placeholder="bash" />
-        </FormField>
+        <>
+          <FormField label="User ID">
+            <InputField
+              form={form}
+              keyboardType="number-pad"
+              name="metadata.user"
+            />
+          </FormField>
+          <FormField label="Shell">
+            <InputField form={form} name="metadata.shell" placeholder="bash" />
+          </FormField>
+        </>
       )}
     </>
   );
diff --git a/frontend/pages/hosts/schema/form.ts b/frontend/pages/hosts/schema/form.ts
index 18bbc4b..f677242 100644
--- a/frontend/pages/hosts/schema/form.ts
+++ b/frontend/pages/hosts/schema/form.ts
@@ -44,6 +44,7 @@ const incusSchema = hostSchema.merge(
     metadata: z.object({
       type: z.enum(["lxc", "qemu"]),
       instance: z.string().min(1, { message: "Instance name is required" }),
+      user: z.coerce.number().nullish(),
       shell: z.string().nullish(),
     }),
   })
diff --git a/server/app/ws/term_incus.go b/server/app/ws/term_incus.go
index 20b8871..ad7cd90 100644
--- a/server/app/ws/term_incus.go
+++ b/server/app/ws/term_incus.go
@@ -15,6 +15,7 @@ import (
 type IncusWebsocketSession struct {
 	Type     string `json:"type"` // "qemu" | "lxc"
 	Instance string `json:"instance"`
+	User     *int   `json:"user"`
 	Shell    string `json:"shell"`
 }
 
@@ -23,7 +24,10 @@ func (i *IncusWebsocketSession) NewTerminal(c *websocket.Conn, incus *lib.IncusS
 		i.Shell = "/bin/sh"
 	}
 
-	exec, err := incus.InstanceExec(i.Instance, []string{i.Shell}, true)
+	exec, err := incus.InstanceExec(i.Instance, []string{i.Shell}, &lib.IncusInstanceExecOptions{
+		Interactive: true,
+		User:        i.User,
+	})
 	if err != nil {
 		return err
 	}
diff --git a/server/lib/incus.go b/server/lib/incus.go
index d75cfc9..958edfb 100644
--- a/server/lib/incus.go
+++ b/server/lib/incus.go
@@ -67,6 +67,11 @@ func (i *IncusServer) Fetch(method string, url string, cfg *IncusFetchConfig) ([
 	return io.ReadAll(resp.Body)
 }
 
+type IncusInstanceExecOptions struct {
+	Interactive bool
+	User        *int
+}
+
 type IncusInstanceExecRes struct {
 	ID        string
 	Operation string
@@ -74,17 +79,23 @@ type IncusInstanceExecRes struct {
 	Secret    string
 }
 
-func (i *IncusServer) InstanceExec(instance string, command []string, interactive bool) (*IncusInstanceExecRes, error) {
+func (i *IncusServer) InstanceExec(instance string, command []string, options *IncusInstanceExecOptions) (*IncusInstanceExecRes, error) {
 	url := fmt.Sprintf("/1.0/instances/%s/exec?project=default", instance)
 
+	var user *int
+	if options != nil && options.User != nil {
+		user = options.User
+	}
+
 	body, err := i.Fetch("POST", url, &IncusFetchConfig{
 		Body: map[string]interface{}{
 			"command":            command,
-			"interactive":        interactive,
+			"interactive":        options != nil && options.Interactive,
 			"wait-for-websocket": true,
 			"environment": map[string]string{
 				"TERM": "xterm-256color",
 			},
+			"user": user,
 		},
 	})
 	if err != nil {