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) => { /> {type === "lxc" && ( - - - + <> + + + + + + + )} ); 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 {