Repository: lima-vm/lima

Issue:
Replace usages of `usr.HomeDir` with `os.UserHomeDir()`
### Description

Right now codebase is using both, but they are not compatible with each other and serve different purposes.

Probably usr.HomeDir was used before os.UserHomeDir was stabilized and available in language.

user.Current utilizes syscalls to fetch user profile and will always return user profile directory, but not the one set in user env.

```sh
user@test-go-home % cat test.go 
package main

import (
	"fmt"
	"os"
	"os/user"
)

func main() {
	dirname, _ := os.UserHomeDir()
	fmt.Println(dirname)

	usr, _ := user.Current()
	fmt.Println(usr.HomeDir)
}

user@test-go-home % go build test.go  

user@test-go-home % ./test  
/Users/user
/Users/user

user@test-go-home % HOME=/tmp ./test   
/tmp
/Users/user
```

It should be straightforward to replace usages of `usr.HomeDir` with `os.UserHomeDir()`

Discussion:
@arixmkii: One of the scenarios for HOME override - when working on integration tests for the template use different home to have another copy of machine from the same template to manually execute commands against it.

The scenario above is specific, but userhome should be consistent between different code paths. So it should be either one or another not a mix of both.@kairveeehh: hii @arixmkii  would like to work on this issue .@arixmkii: @kairveeehh feel free to! Thank you! You can ask maintainers to assign this issue to you.@kairveeehh: > [@kairveeehh](https://github.com/kairveeehh) feel free to! Thank you! You can ask maintainers to assign this issue to you.

Thanks . 
I would like to confirm over one thing that no direct occurrences of `usr.HomeDir ` are there so I suppose we need to change such occurrences  which are in `qemu.go` and other files , I am actually confused here because `usr.HomeDir` and `user.HomeDir` might mean two different things.
```bash 
currentUser, err := user.Current()
	if err != nil {
		return "", err
	}

	const relativePath = "share/qemu/vhost-user"

	binDir := filepath.Dir(qemuExe)                              // "/usr/local/bin"
	usrDir := filepath.Dir(binDir)                               // "/usr/local"
	userLocalDir := filepath.Join(currentUser.HomeDir, ".local"
```
 to 
```bash
currentUser, err := user.Current()
	if err != nil {
		return "", err
	}

	const relativePath = "share/qemu/vhost-user"

	binDir := filepath.Dir(qemuExe)                              // "/usr/local/bin"
	usrDir := filepath.Dir(binDir)                               // "/usr/local"
	omeDir, err := os.UserHomeDir()
	if err != nil {
		t.Fatal(err)
	}
	userLocalDir := filepath.Join(homeDir, ".local") 
```
could you confirm if I am proceeding in the right direction?@arixmkii: I was thinking to check code for `user.Current()` then check if it is only used for the home dir, then replace it with `os.UserHomeDir()`. In case the code is also using other data within struct, then additionally call `os.UserHomeDir()` and update `.HomeDir` value within previously returned struct.@kairveeehh: > I was thinking to check code for `user.Current()` then check if it is only used for the home dir, then replace it with `os.UserHomeDir()`. In case the code is also using other data within struct, then additionally call `os.UserHomeDir()` and update `.HomeDir` value within previously returned struct.

Yes , in this case then I think what I did in the example above would work right , except that I'll keep a check now on `user.Current` ?@afbjorklund: Currently `os.UserHomeDir` doesn't work if HOME is not defined, which is causing Lima to panic in Nix packaging:

https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/virtualization/lima/default.nix

>  `# Workaround for: "panic: $HOME is not defined" at https://github.com/lima-vm/lima/blob/cb99e9f8d01ebb82d000c7912fcadcd87ec13ad5/pkg/limayaml/defaults.go#L53`
>  `# Don't use versionCheckHook for this package. It cannot inject environment variables.`@arixmkii: > Currently os.UserHomeDir doesn't work if HOME is not defined, which is causing Lima to panic in Nix packaging

If this is considered critical. It could be another way around - reverse change, but still make it uniform and consistent.

It is also possible to introduce internal utility function for this - `func HostUserHome() (string, error)` or alike. So, that later implementation could be tweaked and still be consistent through code base.

Current problem is that if $HOME doesn't match user profile home that results in an unexpected behavior with mix of 2 different directories involved.@afbjorklund: I don't think it is critical (they have a workaround), and I think all that is needed is to take care of error instead of panic?@kairveeehh: > I don't think it is critical (they have a workaround), and I think all that is needed is to take care of error instead of panic?

So I should proceed in the direction discussed earlier [comment](https://github.com/lima-vm/lima/issues/3374#issuecomment-2745995409)  or reverse the way around it? 
@afbjorklund: I think you can proceed, most of the time HOME is defined. I saw some similar issues where SHELL was missing too.

Usually it is because we are running in a container, and they get a _pretty_ spartan environment set up by default...

```console
root@a5a7bfe1544d:/# env
PWD=/
HOME=/root
TERM=xterm
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env
```
```console
I have no name!@9c8ce7f1f318:/$ env
PWD=/
HOME=/
TERM=xterm
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env
```


I don't know what cause HOME to be missing in Nix, and I don't really care either. Just saw that it happened.

Solution patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a2b929cc..f322f4c3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -39,7 +39,7 @@ jobs:
     - name: Verify generated files
       run: make install-tools generate check-generated
     - name: Run golangci-lint
-      uses: golangci/golangci-lint-action@dec74fa03096ff515422f71d18d41307cacde373  # v7.0.0
+      uses: golangci/golangci-lint-action@a5307c8f68bcd21ff73283f8629eb09caa60d7a7  # v7.0.0
       with:
         version: v2.0.1
         args: --verbose
@@ -50,7 +50,7 @@ jobs:
         sudo apt-get update
         sudo apt-get install -y shellcheck
     - name: Run file and directory name linter
-      uses: ls-lint/action@1887e6c0e7f2dfa81a2d67591f0eb7782720026f  # v2.2.3
+      uses: ls-lint/action@acc185de566968dd9b3ffc1b79dfc86e22306442  # v2.3.0
     - name: Run shellcheck
       run: find . -name '*.sh' | xargs shellcheck
     - name: Install shfmt
diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go
index f0a92c37..f2fab3a4 100644
--- a/pkg/qemu/qemu.go
+++ b/pkg/qemu/qemu.go
@@ -13,7 +13,6 @@ import (
 	"io/fs"
 	"os"
 	"os/exec"
-	"os/user"
 	"path/filepath"
 	"regexp"
 	"runtime"
@@ -1006,16 +1005,16 @@ func FindVirtiofsd(qemuExe string) (string, error) {
 		Binary      string `json:"binary"`
 	}
 
-	currentUser, err := user.Current()
+	homeDir, err := os.UserHomeDir()
 	if err != nil {
 		return "", err
 	}
 
 	const relativePath = "share/qemu/vhost-user"
 
-	binDir := filepath.Dir(qemuExe)                              // "/usr/local/bin"
-	usrDir := filepath.Dir(binDir)                               // "/usr/local"
-	userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
+	binDir := filepath.Dir(qemuExe)                  // "/usr/local/bin"
+	usrDir := filepath.Dir(binDir)                   // "/usr/local"
+	userLocalDir := filepath.Join(homeDir, ".local") // "$HOME/.local"
 
 	candidates := []string{
 		filepath.Join(userLocalDir, relativePath),
@@ -1175,14 +1174,14 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
 		return "", fmt.Errorf("unexpected architecture: %q", arch)
 	}
 
-	currentUser, err := user.Current()
+	homeDir, err := os.UserHomeDir()
 	if err != nil {
 		return "", err
 	}
 
-	binDir := filepath.Dir(qemuExe)                              // "/usr/local/bin"
-	localDir := filepath.Dir(binDir)                             // "/usr/local"
-	userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
+	binDir := filepath.Dir(qemuExe)                  // "/usr/local/bin"
+	localDir := filepath.Dir(binDir)                 // "/usr/local"
+	userLocalDir := filepath.Join(homeDir, ".local") // "$HOME/.local"
 
 	relativePath := fmt.Sprintf("share/qemu/edk2-%s-code.fd", qemuEdk2Arch(arch))
 	relativePathWin := fmt.Sprintf("share/edk2-%s-code.fd", qemuEdk2Arch(arch))
@@ -1241,14 +1240,14 @@ func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) {
 		return "", fmt.Errorf("unexpected architecture: %q", arch)
 	}
 
-	currentUser, err := user.Current()
+	homeDir, err := os.UserHomeDir()
 	if err != nil {
 		return "", err
 	}
 
-	binDir := filepath.Dir(qemuExe)                              // "/usr/local/bin"
-	localDir := filepath.Dir(binDir)                             // "/usr/local"
-	userLocalDir := filepath.Join(currentUser.HomeDir, ".local") // "$HOME/.local"
+	binDir := filepath.Dir(qemuExe)                  // "/usr/local/bin"
+	localDir := filepath.Dir(binDir)                 // "/usr/local"
+	userLocalDir := filepath.Join(homeDir, ".local") // "$HOME/.local"
 
 	relativePath := fmt.Sprintf("share/qemu/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
 	relativePathWin := fmt.Sprintf("share/edk2-%s-vars.fd", qemuEdk2Arch(targetArch))
diff --git a/pkg/store/instance.go b/pkg/store/instance.go
index 2ddd4380..2af8f814 100644
--- a/pkg/store/instance.go
+++ b/pkg/store/instance.go
@@ -9,7 +9,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"os/user"
 	"path/filepath"
 	"runtime"
 	"strconv"
@@ -355,11 +354,10 @@ func PrintInstances(w io.Writer, instances []*Instance, format string, options *
 		}
 		fmt.Fprintln(w)
 
-		u, err := user.Current()
+		homeDir, err := os.UserHomeDir()
 		if err != nil {
 			return err
 		}
-		homeDir := u.HomeDir
 
 		for _, instance := range instances {
 			dir := instance.Dir
diff --git a/website/content/en/docs/config/network/user.md b/website/content/en/docs/config/network/user.md
index 1dd23fde..24d8859c 100644
--- a/website/content/en/docs/config/network/user.md
+++ b/website/content/en/docs/config/network/user.md
@@ -7,7 +7,7 @@ weight: 30
 By default Lima only enables the user-mode networking aka "slirp".
 
 The subnet is hard-coded to `192.168.5.0/24`.
-Use [`user-v2`]]({{< ref "/docs/config/network/user-v2" >}}) network to customize the subnet.
+Use [`user-v2`]({{< ref "/docs/config/network/user-v2" >}}) network to customize the subnet.
 
 ## Guest IP (192.168.5.15)
 



Test patch:
diff --git a/pkg/store/instance_test.go b/pkg/store/instance_test.go
index b0b87c1c..52a189e3 100644
--- a/pkg/store/instance_test.go
+++ b/pkg/store/instance_test.go
@@ -5,7 +5,7 @@ package store
 
 import (
 	"bytes"
-	"os/user"
+	"os"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -85,10 +85,10 @@ func TestPrintInstanceTableEmu(t *testing.T) {
 
 func TestPrintInstanceTableHome(t *testing.T) {
 	var buf bytes.Buffer
-	u, err := user.Current()
+	homeDir, err := os.UserHomeDir()
 	assert.NilError(t, err)
 	instance1 := instance
-	instance1.Dir = filepath.Join(u.HomeDir, "dir")
+	instance1.Dir = filepath.Join(homeDir, "dir")
 	instances := []*Instance{&instance1}
 	err = PrintInstances(&buf, instances, "table", nil)
 	assert.NilError(t, err)