diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e2ffe19083..b2244b2ea83 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,8 @@ jobs: run: | sudo apt-get update sudo apt-get install -y shellcheck + - name: Run file and directory name linter + uses: ls-lint/action@v2.2.3 - name: Run shellcheck run: find . -name '*.sh' | xargs shellcheck - name: Install shfmt diff --git a/.golangci.yml b/.golangci.yml index 6fdc1a4002d..55582d611ca 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -96,12 +96,42 @@ linters-settings: - rangeValCopy - hugeParam - importShadow - - ifElseChain - sprintfQuotedString - builtinShadow - filepathJoin + settings: + ifElseChain: + # Min number of if-else blocks that makes the warning trigger. + minThreshold: 3 errorlint: asserts: false + revive: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: deep-exit + - name: dot-imports + - name: empty-block + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: increment-decrement + - name: indent-error-flow + - name: package-comments + - name: range + - name: receiver-naming + - name: redefines-builtin-id + - name: superfluous-else + - name: time-naming + - name: unexported-return + - name: unreachable-code + - name: unused-parameter + - name: var-declaration + - name: var-naming issues: # Maximum issues count per one linter. max-issues-per-linter: 0 diff --git a/.ls-lint.yml b/.ls-lint.yml new file mode 100644 index 00000000000..87efb1bc0c5 --- /dev/null +++ b/.ls-lint.yml @@ -0,0 +1,34 @@ +# ls-lint configuration file. +# https://ls-lint.org/2.2/configuration/the-basics.html +ls: + .dir: kebab-case + .go: snake_case + .lima: snake_case + .sh: kebab-case + .TEMPLATE.yaml: kebab-case + .yaml: kebab-case + .yml: kebab-case + + .github: + .yaml: snake_case + + cmd/limactl: + # valid names are `show-ssh.go` or `show-ssh_test.go` + .go: kebab-case | regex:[a-z0-9-]+_test + + docs: + .md: kebab-case + + website/content: + .dir: lowercase + +ignore: +- .git +- .golangci.yml +- .ls-lint.yml +- '_output' +- '**/*.pb\.go' +- hack/common.inc.sh +- pkg/cidata/cidata.TEMPLATE.d +- pkg/cidata/cidata.TEMPLATE.d/util/compare_version.sh +- website diff --git a/Kconfig b/Kconfig index 3e68bc4f2db..3c8853fec5a 100644 --- a/Kconfig +++ b/Kconfig @@ -30,3 +30,9 @@ config GUESTAGENT_ARCH_RISCV64 help Build lima-guestagent for "riscv64" Arch default y + +config GUESTAGENT_COMPRESS + bool "guestagent compress" + help + Compress lima-guestagent + default n diff --git a/Makefile b/Makefile index 9b4506552a1..373dd254bfc 100644 --- a/Makefile +++ b/Makefile @@ -86,22 +86,26 @@ HELPERS = \ _output/bin/podman.lima \ _output/bin/kubectl.lima +ifeq ($(CONFIG_GUESTAGENT_COMPRESS),y) +gz = .gz +endif + ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y) ifeq ($(CONFIG_GUESTAGENT_ARCH_X8664),y) GUESTAGENT += \ - _output/share/lima/lima-guestagent.Linux-x86_64 + _output/share/lima/lima-guestagent.Linux-x86_64$(gz) endif ifeq ($(CONFIG_GUESTAGENT_ARCH_AARCH64),y) GUESTAGENT += \ - _output/share/lima/lima-guestagent.Linux-aarch64 + _output/share/lima/lima-guestagent.Linux-aarch64$(gz) endif ifeq ($(CONFIG_GUESTAGENT_ARCH_ARMV7L),y) GUESTAGENT += \ - _output/share/lima/lima-guestagent.Linux-armv7l + _output/share/lima/lima-guestagent.Linux-armv7l$(gz) endif ifeq ($(CONFIG_GUESTAGENT_ARCH_RISCV64),y) GUESTAGENT += \ - _output/share/lima/lima-guestagent.Linux-riscv64 + _output/share/lima/lima-guestagent.Linux-riscv64$(gz) endif endif @@ -188,6 +192,9 @@ _output/share/lima/lima-guestagent.Linux-riscv64: GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 $(GO_BUILD) -o $@ ./cmd/lima-guestagent chmod 644 $@ +_output/share/lima/lima-guestagent.%.gz: _output/share/lima/lima-guestagent.% + gzip $< + .PHONY: manpages manpages: _output/bin/limactl$(exe) @mkdir -p _output/share/man/man1 diff --git a/README.md b/README.md index 06548dcd433..8057b0c670d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ GUI: ### Code of Conduct Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). +### Environment Variables +For more information on environment variables, see the [Environment Variables](https://lima-vm.io/docs/config/environment-variables/) page. + - - - **We are a [Cloud Native Computing Foundation](https://cncf.io/) sandbox project.** @@ -82,4 +85,4 @@ Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/ -The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/trademark-usage/). +The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/trademark-usage/]. diff --git a/cmd/docker.lima b/cmd/docker.lima old mode 100755 new mode 100644 index b17ac3bf269..841c2783adf --- a/cmd/docker.lima +++ b/cmd/docker.lima @@ -1,5 +1,9 @@ #!/bin/sh set -eu + +# Environment Variables +# LIMA_INSTANCE: Specifies the name of the Lima instance to use. Default is "docker". + : "${LIMA_INSTANCE:=docker}" : "${DOCKER:=docker}" @@ -7,7 +11,7 @@ if [ "$(limactl ls -q "$LIMA_INSTANCE" 2>/dev/null)" != "$LIMA_INSTANCE" ]; then echo "instance \"$LIMA_INSTANCE\" does not exist, run \`limactl create --name=$LIMA_INSTANCE template://docker\` to create a new instance" >&2 exit 1 elif [ "$(limactl ls -f '{{ .Status }}' "$LIMA_INSTANCE" 2>/dev/null)" != "Running" ]; then - echo "instance \"$LIMA_INSTANCE\" is not running, run \`limactl start $LIMA_INSTANCE\` to start the existing instance" >&2 + echo "instance \"$LIMA_INSTANCE\"" is not running, run \`limactl start $LIMA_INSTANCE\` to start the existing instance" >&2 exit 1 fi DOCKER=$(command -v "$DOCKER" || true) diff --git a/cmd/kubectl.lima b/cmd/kubectl.lima old mode 100755 new mode 100644 index 70236ad3ee8..40822e8c973 --- a/cmd/kubectl.lima +++ b/cmd/kubectl.lima @@ -1,5 +1,9 @@ #!/bin/sh set -eu + +# Environment Variables +# LIMA_INSTANCE: Specifies the name of the Lima instance to use. Default is empty. + : "${LIMA_INSTANCE:=}" : "${KUBECTL:=kubectl}" diff --git a/cmd/lima b/cmd/lima old mode 100755 new mode 100644 index b78c454594e..f26202c935b --- a/cmd/lima +++ b/cmd/lima @@ -1,5 +1,12 @@ #!/bin/sh set -eu + +# Environment Variables +# LIMA_INSTANCE: Specifies the name of the Lima instance to use. Default is "default". +# LIMA_SHELL: Specifies the shell interpreter to use inside the Lima instance. Default is the user's shell configured inside the instance. +# LIMA_WORKDIR: Specifies the initial working directory inside the Lima instance. Default is the current directory from the host. +# LIMACTL: Specifies the path to the limactl binary. Default is "limactl" in $PATH. + : "${LIMA_INSTANCE:=default}" : "${LIMA_SHELL:=}" : "${LIMA_WORKDIR:=}" diff --git a/cmd/lima.bat b/cmd/lima.bat old mode 100755 new mode 100644 index c73d453e8e8..d624000d846 --- a/cmd/lima.bat +++ b/cmd/lima.bat @@ -1,4 +1,8 @@ -@echo off -IF NOT DEFINED LIMACTL (SET LIMACTL=limactl) -IF NOT DEFINED LIMA_INSTANCE (SET LIMA_INSTANCE=default) -%LIMACTL% shell %LIMA_INSTANCE% %* +@echo off +REM Environment Variables +REM LIMA_INSTANCE: Specifies the name of the Lima instance to use. Default is "default". +REM LIMACTL: Specifies the path to the limactl binary. Default is "limactl" in %PATH%. + +IF NOT DEFINED LIMACTL (SET LIMACTL=limactl) +IF NOT DEFINED LIMA_INSTANCE (SET LIMA_INSTANCE=default) +%LIMACTL% shell %LIMA_INSTANCE% %* diff --git a/cmd/limactl/list.go b/cmd/limactl/list.go index 3be0b888ef6..f94060e65ba 100644 --- a/cmd/limactl/list.go +++ b/cmd/limactl/list.go @@ -73,6 +73,19 @@ func instanceMatches(arg string, instances []string) []string { return matches } +// unmatchedInstancesError is created when unmatched instance names found. +type unmatchedInstancesError struct{} + +// Error implements error. +func (unmatchedInstancesError) Error() string { + return "unmatched instances" +} + +// ExitCode implements ExitCoder. +func (unmatchedInstancesError) ExitCode() int { + return 1 +} + func listAction(cmd *cobra.Command, args []string) error { quiet, err := cmd.Flags().GetBool("quiet") if err != nil { @@ -148,7 +161,7 @@ func listAction(cmd *cobra.Command, args []string) error { fmt.Fprintln(cmd.OutOrStdout(), instName) } if unmatchedInstances { - os.Exit(1) + return unmatchedInstancesError{} } return nil } @@ -186,7 +199,7 @@ func listAction(cmd *cobra.Command, args []string) error { err = store.PrintInstances(out, instances, format, &options) if err == nil && unmatchedInstances { - os.Exit(1) + return unmatchedInstancesError{} } return err } diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go index e6266c4f90d..0c1d14788bd 100644 --- a/cmd/limactl/main.go +++ b/cmd/limactl/main.go @@ -156,7 +156,7 @@ func handleExitCoder(err error) { } if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) + os.Exit(exitErr.ExitCode()) //nolint:revive // it's intentional to call os.Exit in this function return } } diff --git a/cmd/limactl/show_ssh.go b/cmd/limactl/show-ssh.go similarity index 100% rename from cmd/limactl/show_ssh.go rename to cmd/limactl/show-ssh.go diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index 89587af831b..1fa2900e7b7 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -128,9 +128,11 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* const yBytesLimit = 4 * 1024 * 1024 // 4MiB - if ok, u := guessarg.SeemsTemplateURL(arg); ok { + isTemplateURL, templateURL := guessarg.SeemsTemplateURL(arg) + switch { + case isTemplateURL: // No need to use SecureJoin here. https://github.com/lima-vm/lima/pull/805#discussion_r853411702 - templateName := filepath.Join(u.Host, u.Path) + templateName := filepath.Join(templateURL.Host, templateURL.Path) logrus.Debugf("interpreting argument %q as a template name %q", arg, templateName) if st.instName == "" { // e.g., templateName = "deprecated/centos-7" , st.instName = "centos-7" @@ -140,7 +142,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* if err != nil { return nil, err } - } else if guessarg.SeemsHTTPURL(arg) { + case guessarg.SeemsHTTPURL(arg): if st.instName == "" { st.instName, err = guessarg.InstNameFromURL(arg) if err != nil { @@ -161,7 +163,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* if err != nil { return nil, err } - } else if guessarg.SeemsFileURL(arg) { + case guessarg.SeemsFileURL(arg): if st.instName == "" { st.instName, err = guessarg.InstNameFromURL(arg) if err != nil { @@ -178,7 +180,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* if err != nil { return nil, err } - } else if guessarg.SeemsYAMLPath(arg) { + case guessarg.SeemsYAMLPath(arg): if st.instName == "" { st.instName, err = guessarg.InstNameFromYAMLPath(arg) if err != nil { @@ -195,7 +197,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* if err != nil { return nil, err } - } else if arg == "-" { + case arg == "-": if st.instName == "" { return nil, errors.New("must pass instance name with --name when reading template from stdin") } @@ -209,7 +211,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* return nil, errors.New("cannot use --tty=true and read template from stdin together") } tty = false - } else { + default: if arg == "" { if st.instName == "" { st.instName = DefaultInstanceName @@ -292,6 +294,18 @@ func applyYQExpressionToExistingInstance(inst *store.Instance, yq string) (*stor if err != nil { return nil, err } + y, err := limayaml.Load(yBytes, filePath) + if err != nil { + return nil, err + } + if err := limayaml.Validate(y, true); err != nil { + rejectedYAML := "lima.REJECTED.yaml" + if writeErr := os.WriteFile(rejectedYAML, yBytes, 0o644); writeErr != nil { + return nil, fmt.Errorf("the YAML is invalid, attempted to save the buffer as %q but failed: %w: %w", rejectedYAML, writeErr, err) + } + // TODO: may need to support editing the rejected YAML + return nil, fmt.Errorf("the YAML is invalid, saved the buffer as %q: %w", rejectedYAML, err) + } if err := os.WriteFile(filePath, yBytes, 0o644); err != nil { return nil, err } @@ -373,6 +387,21 @@ func modifyInPlace(st *creatorState, yq string) error { return nil } +// exitSuccessError is an error that indicates a successful exit. +type exitSuccessError struct { + Msg string +} + +// Error implements error. +func (e exitSuccessError) Error() string { + return e.Msg +} + +// ExitCode implements ExitCoder. +func (exitSuccessError) ExitCode() int { + return 0 +} + func chooseNextCreatorState(st *creatorState, yq string) (*creatorState, error) { for { if err := modifyInPlace(st, yq); err != nil { @@ -411,9 +440,9 @@ func chooseNextCreatorState(st *creatorState, yq string) (*creatorState, error) return st, err } if len(st.yBytes) == 0 { - logrus.Info("Aborting, as requested by saving the file with empty content") - os.Exit(0) - return st, errors.New("should not reach here") + const msg = "Aborting, as requested by saving the file with empty content" + logrus.Info(msg) + return nil, exitSuccessError{Msg: msg} } return st, nil case 2: // "Choose another template..." @@ -446,8 +475,7 @@ func chooseNextCreatorState(st *creatorState, yq string) (*creatorState, error) } continue case 3: // "Exit" - os.Exit(0) - return st, errors.New("should not reach here") + return nil, exitSuccessError{Msg: "Choosing to exit"} default: return st, fmt.Errorf("unexpected answer %q", ans) } diff --git a/cmd/limactl/stop.go b/cmd/limactl/stop.go index 8dffb86c164..ecfa2277aff 100644 --- a/cmd/limactl/stop.go +++ b/cmd/limactl/stop.go @@ -75,7 +75,7 @@ func stopInstanceGracefully(inst *store.Instance) error { } func waitForHostAgentTermination(ctx context.Context, inst *store.Instance, begin time.Time) error { - ctx2, cancel := context.WithTimeout(ctx, 3*time.Minute) + ctx2, cancel := context.WithTimeout(ctx, 3*time.Minute+10*time.Second) defer cancel() var receivedExitingEvent bool diff --git a/cmd/limactl/usernet.go b/cmd/limactl/usernet.go index 704c268a65f..5ad356f4464 100644 --- a/cmd/limactl/usernet.go +++ b/cmd/limactl/usernet.go @@ -73,6 +73,9 @@ func usernetAction(cmd *cobra.Command, _ []string) error { os.RemoveAll(qemuSocket) os.RemoveAll(fdSocket) + // Environment Variables + // LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT: Specifies the timeout duration for resolving IP addresses in minutes. Default is 2 minutes. + return usernet.StartGVisorNetstack(cmd.Context(), &usernet.GVisorNetstackOpts{ MTU: mtu, Endpoint: endpoint, diff --git a/config.mk b/config.mk index 89f5488fe55..7b3eb49e2f9 100644 --- a/config.mk +++ b/config.mk @@ -3,3 +3,4 @@ CONFIG_GUESTAGENT_ARCH_X8664=y CONFIG_GUESTAGENT_ARCH_AARCH64=y CONFIG_GUESTAGENT_ARCH_ARMV7L=y CONFIG_GUESTAGENT_ARCH_RISCV64=y +CONFIG_GUESTAGENT_COMPRESS=n diff --git a/examples/README.md b/examples/README.md index 866e53f031b..5803c9a0567 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,7 +15,6 @@ Distro: - [`almalinux-9`](./almalinux-9.yaml), `almalinux.yaml`: AlmaLinux 9 - [`alpine`](./alpine.yaml): ☆Alpine Linux - [`archlinux`](./archlinux.yaml): ⭐Arch Linux -- [`centos-stream-8`](./centos-stream-8.yaml): CentOS Stream 8 - [`centos-stream-9`](./centos-stream-9.yaml), `centos-stream.yaml`: CentOS Stream 9 - [`debian-11`](./debian-11.yaml): Debian GNU/Linux 11(bullseye) - [`debian-12`](./debian-12.yaml), `debian.yaml`: ⭐Debian GNU/Linux 12(bookworm) @@ -27,7 +26,6 @@ Distro: - [`rocky-9`](./rocky-9.yaml), `rocky.yaml`: Rocky Linux 9 - [`ubuntu`](./ubuntu.yaml): Ubuntu (same as `default.yaml` but without extra YAML lines) - [`ubuntu-lts`](./ubuntu-lts.yaml): Ubuntu LTS (same as `ubuntu.yaml` but pinned to an LTS version) -- [`deprecated/centos-7`](./deprecated/centos-7.yaml): [deprecated] CentOS Linux 7 - [`experimental/gentoo`](./experimental/gentoo.yaml): [experimental] Gentoo - [`experimental/opensuse-tumbleweed`](./experimental/opensuse-tumbleweed.yaml): [experimental] openSUSE Tumbleweed @@ -58,6 +56,7 @@ Optional feature enablers: - [`experimental/net-user-v2`](./experimental/net-user-v2.yaml): [experimental] user-v2 network to enable VM-to-VM communication without root privilege - [`experimental/vnc`](./experimental/vnc.yaml): [experimental] use vnc display and xorg server +- [`experimental/alsa`](./experimental/alsa.yaml): [experimental] use alsa and default audio device Lost+found: - ~`centos`~: Removed in Lima v0.8.0, as CentOS 8 reached [EOL](https://www.centos.org/centos-linux-eol/). @@ -68,6 +67,8 @@ Lost+found: - ~`experimental/{almalinux,centos-stream-9,oraclelinux,rocky}-9`~: Moved to [`almalinux-9`](./almalinux-9.yaml), [`centos-stream-9`](./centos-stream-9.yaml), [`oraclelinux-9`](./oraclelinux-9.yaml), and [`rocky-9`](./rocky-9.yaml) in Lima v0.13.0. - ~`nomad`~: Removed in Lima v0.17.1, as Nomad is [no longer free software](https://github.com/hashicorp/nomad/commit/b3e30b1dfa185d9437a25830522da47b91f78816) +- ~`centos-stream-8`~: Remove in Lima v0.23.0, as CentOS Stream 8 reached [EOL](https://blog.centos.org/2023/04/end-dates-are-coming-for-centos-stream-8-and-centos-linux-7/). +- ~`deprecated/centos-7`~: Remove in Lima v0.23.0, as CentOS 7 reached [EOL](https://blog.centos.org/2023/04/end-dates-are-coming-for-centos-stream-8-and-centos-linux-7/). ## Tier diff --git a/examples/alpine-image.yaml b/examples/alpine-image.yaml index e83b6e435c9..583749a85a0 100644 --- a/examples/alpine-image.yaml +++ b/examples/alpine-image.yaml @@ -1,10 +1,10 @@ images: -- location: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-x86_64-uefi-cloudinit-r0.qcow2" +- location: "https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.1-x86_64-uefi-cloudinit-r0.qcow2" arch: "x86_64" - digest: "sha512:bd9e0d84970c91c5699f9ece5df071f8254e8e63a6e53f5dfb4bb8e2eb19f8ce9749dc768fe8a338d91b93555f96d601c4a559510f06a627a63437c81534335d" -- location: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-aarch64-uefi-cloudinit-r0.qcow2" + digest: "sha512:bb7c0ca7a6d064b5c17ef203a14479c070f95c3c326b82440d96c29257f33312aa0b01ee7c1858a04c54a32368524517592b44222bb88496f4a4d6cabfbe05dd" +- location: "https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.1-aarch64-uefi-cloudinit-r0.qcow2" arch: "aarch64" - digest: "sha512:d5e69cff0ecb0fd3850bd78f56f66131115934df27b2373c35a85a74b9def52822134dd43f90b4fabe239fdd4452026cb45f1c8f0b36a43af69335a26f0959b5" + digest: "sha512:096a98b309a32628b7f279181916015dce02a59374ad229658b4375c036608fb50c3ad2a9470ca4be359af58a30ee7e682752b11402acc59a6c68bebe0aa666e" mounts: - location: "~" diff --git a/examples/centos-stream-8.yaml b/examples/centos-stream-8.yaml deleted file mode 100644 index dc8a380f997..00000000000 --- a/examples/centos-stream-8.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# This template requires Lima v0.8.3 or later. - -# NOTE: EL8-based distros are known not to work on M1 chips: https://github.com/lima-vm/lima/issues/841 -# EL9-based distros are known to work. - -images: -# Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months. -- location: "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20240429.0.x86_64.qcow2" - arch: "x86_64" - digest: "sha256:dd4d28d3ff4e1e9e18748fdc1daae1f5502b93663fbd8c0a6072822fd0afd7e5" -- location: "https://cloud.centos.org/centos/8-stream/aarch64/images/CentOS-Stream-GenericCloud-8-20240429.0.aarch64.qcow2" - arch: "aarch64" - digest: "sha256:d72dc4d34920150e5bda2a29be8faef23639e02e95421a03c8df300a49436ff2" -# Fallback to the latest release image. -# Hint: run `limactl prune` to invalidate the cache -- location: "https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" - arch: "x86_64" -- location: "https://cloud.centos.org/centos/8-stream/aarch64/images/CentOS-Stream-GenericCloud-8-latest.aarch64.qcow2" - arch: "aarch64" -mounts: -- location: "~" -- location: "/tmp/lima" - writable: true -firmware: - # CentOS Stream 8 still requires legacyBIOS, while AlmaLinux 8 and Rocky Linux 8 do not. - legacyBIOS: true -cpuType: - # Workaround for "vmx_write_mem: mmu_gva_to_gpa XXXXXXXXXXXXXXXX failed" on Intel Mac - # https://bugs.launchpad.net/qemu/+bug/1838390 - x86_64: "Haswell-v4" diff --git a/examples/default.yaml b/examples/default.yaml index 8c354e2cf09..36d04ac95f5 100644 --- a/examples/default.yaml +++ b/examples/default.yaml @@ -125,7 +125,7 @@ ssh: # 🟢 Builtin default: 0 (automatically assigned to a free port) # NOTE: when the instance name is "default", the builtin default value is set to # 60022 for backward compatibility. - localPort: 0 + localPort: null # Load ~/.ssh/*.pub in addition to $LIMA_HOME/_config/user.pub . # This option is useful when you want to use other SSH-based # applications such as rsync with the Lima instance. @@ -297,6 +297,9 @@ audio: # QEMU audiodev, e.g., "none", "coreaudio", "pa", "alsa", "oss". # VZ driver, use "vz" as device name # Choosing "none" will mute the audio output, and not play any sound. + # Choosing "default" will pick a suitable of: coreudio, pa, dsound, oss. + # As of QEMU v6.2 the default is to create a disconnected sound device + # that is still visible in the guest but not connected to the host. # 🟢 Builtin default: "" device: null diff --git a/examples/deprecated/centos-7.yaml b/examples/deprecated/centos-7.yaml deleted file mode 100644 index de4a37b8733..00000000000 --- a/examples/deprecated/centos-7.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# This template requires Lima v0.10.0 or later. -# WARNING: RHEL-like below version 8 is unsupported, use at your own risk! -images: -- location: "https://cloud.centos.org/altarch/7/images/CentOS-7-x86_64-GenericCloud-2009.qcow2" - arch: "x86_64" - digest: "sha256:e38bab0475cc6d004d2e17015969c659e5a308111851b0e2715e84646035bdd3" -- location: "https://cloud.centos.org/altarch/7/images/CentOS-7-aarch64-GenericCloud-2009.qcow2" - arch: "aarch64" - digest: "sha256:51c0222aa4bc7d966fc044eb6ce9182993a1dc398eaa595e58abd0d361439baf" -# CentOS7 doesn't have support to systemd-user (https://bugzilla.redhat.com/show_bug.cgi?id=1173278) -# containerd as system works, but you'd need to use nerdctl in the shell of the instance as root -containerd: - system: false - user: false -mounts: -- location: "~" -- location: "/tmp/lima" - writable: true -firmware: - legacyBIOS: true -cpuType: - # Workaround for "vmx_write_mem: mmu_gva_to_gpa XXXXXXXXXXXXXXXX failed" on Intel Mac - # https://bugs.launchpad.net/qemu/+bug/1838390 - x86_64: "Haswell-v4" diff --git a/examples/docker-rootful.yaml b/examples/docker-rootful.yaml index ce254dbf676..4ddd2c937d7 100644 --- a/examples/docker-rootful.yaml +++ b/examples/docker-rootful.yaml @@ -66,7 +66,7 @@ probes: echo >&2 "dockerd is not running" exit 1 fi - hint: See "/var/log/cloud-init-output.log". in the guest + hint: See "/var/log/cloud-init-output.log" in the guest hostResolver: # hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also # resolve inside containers, and not just inside the VM itself. diff --git a/examples/docker.yaml b/examples/docker.yaml index 0ce8eeeec50..4f5e8fe562f 100644 --- a/examples/docker.yaml +++ b/examples/docker.yaml @@ -68,7 +68,7 @@ probes: echo >&2 "rootlesskit (used by rootless docker) is not running" exit 1 fi - hint: See "/var/log/cloud-init-output.log". in the guest + hint: See "/var/log/cloud-init-output.log" in the guest hostResolver: # hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also # resolve inside containers, and not just inside the VM itself. diff --git a/examples/experimental/alsa.yaml b/examples/experimental/alsa.yaml new file mode 100644 index 00000000000..00f24124096 --- /dev/null +++ b/examples/experimental/alsa.yaml @@ -0,0 +1,55 @@ +# A template to run ubuntu using device: default +# This template requires Lima v0.23.0 or later. +images: +# Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months. +- location: "https://cloud-images.ubuntu.com/releases/24.04/release-20240423/ubuntu-24.04-server-cloudimg-amd64.img" + arch: "x86_64" + digest: "sha256:32a9d30d18803da72f5936cf2b7b9efcb4d0bb63c67933f17e3bdfd1751de3f3" +- location: "https://cloud-images.ubuntu.com/releases/24.04/release-20240423/ubuntu-24.04-server-cloudimg-arm64.img" + arch: "aarch64" + digest: "sha256:c841bac00925d3e6892d979798103a867931f255f28fefd9d5e07e3e22d0ef22" +# Fallback to the latest release image. +# Hint: run `limactl prune` to invalidate the cache +- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img" + arch: "x86_64" +- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img" + arch: "aarch64" + +mounts: +- location: "~" +- location: "/tmp/lima" + writable: true + +vmType: "qemu" +audio: + device: "default" + +provision: +- mode: system + script: | + #!/bin/bash + set -eux -o pipefail + test -e /lib/modules/$(uname -r)/kernel/sound/pci/hda/snd-hda-intel.ko* && exit 0 + apt-get install -y linux-modules-extra-$(uname -r) + modprobe snd-hda-intel +- mode: system + script: | + #!/bin/bash + set -eux -o pipefail + command -v aplay >/dev/null 2>&1 && exit 0 + apt-get install -y --no-install-recommends alsa-utils +probes: +- description: "alsa to be installed" + script: | + #!/bin/bash + set -eux -o pipefail + if ! timeout 30s bash -c "until command -v aplay >/dev/null 2>&1; do sleep 3; done"; then + echo >&2 "alsa is not installed yet" + exit 1 + fi + hint: See "/var/log/cloud-init-output.log" in the guest +message: | + To get a list of all available audio devices: + $ sudo aplay -L + To test the audio device, use something like: + $ sudo speaker-test -c2 -twav diff --git a/examples/experimental/u7s.yaml b/examples/experimental/u7s.yaml index e32b5903a50..470b240de74 100644 --- a/examples/experimental/u7s.yaml +++ b/examples/experimental/u7s.yaml @@ -100,7 +100,7 @@ probes: exit 1 fi hint: | - See "/var/log/cloud-init-output.log". in the guest + See "/var/log/cloud-init-output.log" in the guest - description: "kubeadm to be completed" script: | #!/bin/bash diff --git a/examples/experimental/vnc.yaml b/examples/experimental/vnc.yaml index fe0f0b6641a..0f5ffdd464b 100644 --- a/examples/experimental/vnc.yaml +++ b/examples/experimental/vnc.yaml @@ -53,7 +53,7 @@ probes: echo >&2 "Xorg is not installed yet" exit 1 fi - hint: See "/var/log/cloud-init-output.log". in the guest + hint: See "/var/log/cloud-init-output.log" in the guest message: | Use a VNC viewer or noVNC, to connect to the display: diff --git a/examples/k8s.yaml b/examples/k8s.yaml index cd976de74c4..42ac3516d2d 100644 --- a/examples/k8s.yaml +++ b/examples/k8s.yaml @@ -144,7 +144,7 @@ probes: exit 1 fi hint: | - See "/var/log/cloud-init-output.log". in the guest + See "/var/log/cloud-init-output.log" in the guest - description: "kubeadm to be completed" script: | #!/bin/bash diff --git a/go.mod b/go.mod index ac31b6960ac..a02b15a2473 100644 --- a/go.mod +++ b/go.mod @@ -10,18 +10,18 @@ require ( github.com/apparentlymart/go-cidr v1.1.0 github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.18 + github.com/containerd/containerd v1.7.19 github.com/containerd/continuity v0.4.3 github.com/containers/gvisor-tap-vsock v0.7.3 github.com/coreos/go-semver v0.3.1 github.com/cpuguy83/go-md2man/v2 v2.0.4 - github.com/cyphar/filepath-securejoin v0.2.5 + github.com/cyphar/filepath-securejoin v0.3.0 github.com/digitalocean/go-qemu v0.0.0-20221209210016-f035778c97f7 github.com/diskfs/go-diskfs v1.4.0 github.com/docker/go-units v0.5.0 github.com/elastic/go-libaudit/v2 v2.5.0 github.com/foxcpp/go-mockdns v1.1.0 - github.com/goccy/go-yaml v1.11.3 + github.com/goccy/go-yaml v1.12.0 github.com/google/go-cmp v0.6.0 github.com/lima-vm/go-qcow2reader v0.1.1 github.com/lima-vm/sshocker v0.3.4 @@ -34,15 +34,15 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/rjeczalik/notify v0.9.3 - github.com/sethvargo/go-password v0.2.0 + github.com/sethvargo/go-password v0.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 - golang.org/x/net v0.26.0 + golang.org/x/net v0.27.0 golang.org/x/sync v0.7.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.22.0 golang.org/x/text v0.16.0 - google.golang.org/grpc v1.64.0 + google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v3 v3.0.1 @@ -106,15 +106,14 @@ require ( github.com/yuin/gopher-lua v1.1.1 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.7.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.22.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index c02f9bc6e93..37e8dd21193 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e h1:IdMhFPEfTZQU971tI github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e/go.mod h1:aXGMJsd3XrnUFTuyf/pTGg5jG6CY8JMZ5juywvShjgQ= github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk= github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI= -github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= -github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= +github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= @@ -42,8 +42,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.0 h1:tXpmbiaeBrS/K2US8nhgwdKYnfAOnVfkcLPKFgFHeA0= +github.com/cyphar/filepath-securejoin v0.3.0/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -94,12 +94,10 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= -github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= @@ -111,7 +109,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -240,8 +237,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= -github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= +github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= +github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -284,8 +281,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -321,10 +318,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -371,8 +368,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -381,13 +378,12 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= 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= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -417,14 +413,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/00-alpine-user-group.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/00-alpine-user-group.sh new file mode 100644 index 00000000000..514a8bcfcc8 --- /dev/null +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/00-alpine-user-group.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Remove the user embedded in the image, +# and use cloud-init for users and groups. +test -f /etc/alpine-release || exit 0 +test "$LIMA_CIDATA_USER" != "alpine" || exit 0 + +if [ "$(id -u alpine 2>&1)" = "1000" ]; then + userdel alpine + rmdir /home/alpine + cloud-init clean --logs + reboot +fi diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/04-persistent-data-volume.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/04-persistent-data-volume.sh index 072eb8a2913..423538f3d83 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/boot/04-persistent-data-volume.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/04-persistent-data-volume.sh @@ -32,6 +32,10 @@ for DIR in ${DATADIRS}; do [ "${MNTTYPE}" = "ext4" ] && continue [ "${MNTTYPE}" = "tmpfs" ] && continue MNTOPTS="$(echo "${LINE}" | awk '{print $4}')" + if [ "${MNTTYPE}" = "9p" ]; then + # https://github.com/torvalds/linux/blob/v6.6/fs/9p/v9fs.h#L61 + MNTOPTS="$(echo "${MNTOPTS}" | sed -e 's/cache=8f,/cache=fscache,/; s/cache=f,/cache=loose,/; s/cache=5,/cache=mmap,/; s/cache=1,/cache=readahead,/; s/cache=0,/cache=none,/')" + fi # Before mv, unmount filesystems (virtiofs, 9p, etc.) below "${DIR}", otherwise host mounts will be wiped out # https://github.com/rancher-sandbox/rancher-desktop/issues/6582 umount "${MNTPNT}" || exit 1 diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh index 2ad4719f0ad..8be8420d242 100755 --- a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh @@ -7,29 +7,24 @@ if [ "$LIMA_CIDATA_ROSETTA_ENABLED" != "true" ]; then fi if [ -f /etc/alpine-release ]; then + rc-service procfs start --ifnotstarted rc-service qemu-binfmt stop --ifstarted fi -mkdir -p /mnt/lima-rosetta +binfmt_entry=/proc/sys/fs/binfmt_misc/rosetta +binfmtd_conf=/usr/lib/binfmt.d/rosetta.conf +if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then + rosetta_binfmt=":rosetta:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/mnt/lima-rosetta/rosetta:OCF" -#Check selinux is enabled by kernel -if [ -d /sys/fs/selinux ]; then - ########################################################################################## - ## When using vz & virtiofs, initially container_file_t selinux label - ## was considered which works perfectly for container work loads - ## but it might break for other work loads if the process is running with - ## different label. Also these are the remote mounts from the host machine, - ## so keeping the label as nfs_t fits right. Package container-selinux by - ## default adds rules for nfs_t context which allows container workloads to work as well. - ## https://github.com/lima-vm/lima/pull/1965 - ########################################################################################## - mount -t virtiofs vz-rosetta /mnt/lima-rosetta -o context="system_u:object_r:nfs_t:s0" -else - mount -t virtiofs vz-rosetta /mnt/lima-rosetta -fi + # If rosetta is not registered in binfmt_misc, register it. + [ -f "$binfmt_entry" ] || echo "$rosetta_binfmt" >/proc/sys/fs/binfmt_misc/register -if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then - echo \ - ':rosetta:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/mnt/lima-rosetta/rosetta:OCF' \ - >/proc/sys/fs/binfmt_misc/register + # Create binfmt.d(5) configuration to prioritize rosetta even if qemu-user-static is installed on systemd based systems. + # If the binfmt.d directory exists, consider systemd-binfmt.service(8) to be enabled and create the configuration file. + [ ! -d "$(dirname "$binfmtd_conf")" ] || [ -f "$binfmtd_conf" ] || echo "$rosetta_binfmt" >"$binfmtd_conf" +else + # unregister rosetta from binfmt_misc if it exists + [ ! -f "$binfmt_entry" ] || echo -1 | "$binfmt_entry" + # remove binfmt.d(5) configuration if it exists + [ ! -f "$binfmtd_conf" ] || rm "$binfmtd_conf" fi diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/40-install-containerd.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/40-install-containerd.sh index 1f5f004c5ae..b2c6ad6ab0a 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/boot/40-install-containerd.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/40-install-containerd.sh @@ -1,5 +1,8 @@ #!/bin/bash set -eux +: "${CONTAINERD_NAMESPACE:=default}" +# Overridable in .bashrc +: "${CONTAINERD_SNAPSHOTTER:=overlayfs}" if [ "${LIMA_CIDATA_CONTAINERD_SYSTEM}" != 1 ] && [ "${LIMA_CIDATA_CONTAINERD_USER}" != 1 ]; then exit 0 @@ -20,7 +23,8 @@ if [ ! -f "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/nerdctl ] || [[ "${tmp_extra echo "Upgrading existing nerdctl" echo "- Old: $("${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/nerdctl --version)" echo "- New: $("${tmp_extract_nerdctl}"/bin/nerdctl --version)" - systemctl disable --now containerd buildkit stargz-snapshotter + systemctl disable --now containerd default-buildkit stargz-snapshotter + sudo -iu "${LIMA_CIDATA_USER}" "XDG_RUNTIME_DIR=/run/user/${LIMA_CIDATA_UID}" "PATH=${PATH}" "CONTAINERD_NAMESPACE=${CONTAINERD_NAMESPACE}" containerd-rootless-setuptool.sh uninstall-buildkit-containerd sudo -iu "${LIMA_CIDATA_USER}" "XDG_RUNTIME_DIR=/run/user/${LIMA_CIDATA_UID}" "PATH=${PATH}" containerd-rootless-setuptool.sh uninstall ) fi @@ -33,10 +37,6 @@ fi rm -rf "${tmp_extract_nerdctl}" -: "${CONTAINERD_NAMESPACE:=default}" -# Overridable in .bashrc -: "${CONTAINERD_SNAPSHOTTER:=overlayfs}" - if [ "${LIMA_CIDATA_CONTAINERD_SYSTEM}" = 1 ]; then mkdir -p /etc/containerd /etc/buildkit cat >"/etc/containerd/config.toml" < 0 { + switch { + case len(y.DNS) > 0: for _, addr := range y.DNS { args.DNSAddresses = append(args.DNSAddresses, addr.String()) } - } else if firstUsernetIndex != -1 || *y.VMType == limayaml.VZ { + case firstUsernetIndex != -1 || *y.VMType == limayaml.VZ: args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) - } else if *y.HostResolver.Enabled { + case *y.HostResolver.Enabled: args.UDPDNSLocalPort = udpDNSLocalPort args.TCPDNSLocalPort = tcpDNSLocalPort args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) - } else { + default: args.DNSAddresses, err = osutil.DNSAddresses() if err != nil { return err @@ -341,14 +343,30 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort } } - guestAgentBinary, err := GuestAgentBinary(*y.OS, *y.Arch) + guestAgentBinary, err := usrlocalsharelima.GuestAgentBinary(*y.OS, *y.Arch) if err != nil { return err } - defer guestAgentBinary.Close() + var guestAgent io.ReadCloser + guestAgent, err = os.Open(guestAgentBinary) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + compressedGuestAgent, err := os.Open(guestAgentBinary + ".gz") + if err != nil { + return err + } + logrus.Debugf("Decompressing %s.gz", guestAgentBinary) + guestAgent, err = gzip.NewReader(compressedGuestAgent) + if err != nil { + return err + } + } + defer guestAgent.Close() layout = append(layout, iso9660util.Entry{ Path: "lima-guestagent", - Reader: guestAgentBinary, + Reader: guestAgent, }) if nerdctlArchive != "" { @@ -375,21 +393,6 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort return iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout) } -func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (io.ReadCloser, error) { - if ostype == "" { - return nil, errors.New("os must be set") - } - if arch == "" { - return nil, errors.New("arch must be set") - } - dir, err := usrlocalsharelima.Dir() - if err != nil { - return nil, err - } - gaPath := filepath.Join(dir, "lima-guestagent."+ostype+"-"+arch) - return os.Open(gaPath) -} - func getCert(content string) Cert { lines := []string{} for _, line := range strings.Split(content, "\n") { diff --git a/pkg/cidata/fuzz_test.go b/pkg/cidata/fuzz_test.go new file mode 100644 index 00000000000..cb5bf769b5c --- /dev/null +++ b/pkg/cidata/fuzz_test.go @@ -0,0 +1,27 @@ +package cidata + +import ( + "fmt" + "testing" + + "github.com/lima-vm/lima/pkg/networks" + "github.com/lima-vm/lima/pkg/ptr" + + "github.com/lima-vm/lima/pkg/limayaml" +) + +func FuzzSetupEnv(f *testing.F) { + f.Fuzz(func(_ *testing.T, suffix string, localhost bool) { + var prefix string + if localhost { + prefix = "http://localhost:8080/" + } else { + prefix = "http://127.0.0.1:8080/" + } + envKey := "http_proxy" + envValue := fmt.Sprintf("%s%s", prefix, suffix) + templateArgs := TemplateArgs{SlirpGateway: networks.SlirpGateway} + envAttr := map[string]string{envKey: envValue} + _, _ = setupEnv(&limayaml.LimaYAML{PropagateProxyEnv: ptr.Of(false), Env: envAttr}, templateArgs) + }) +} diff --git a/pkg/downloader/downloader.go b/pkg/downloader/downloader.go index 10c6c5f8d73..aa79c68febf 100644 --- a/pkg/downloader/downloader.go +++ b/pkg/downloader/downloader.go @@ -13,6 +13,7 @@ import ( "path" "path/filepath" "strings" + "time" "github.com/cheggaaa/pb/v3" "github.com/containerd/continuity/fs" @@ -44,6 +45,8 @@ const ( type Result struct { Status Status CachePath string // "/Users/foo/Library/Caches/lima/download/by-url-sha256//data" + LastModified time.Time + ContentType string ValidatedDigest bool } @@ -118,6 +121,38 @@ func WithExpectedDigest(expectedDigest digest.Digest) Opt { } } +func readFile(path string) string { + if path == "" { + return "" + } + if _, err := os.Stat(path); err != nil { + return "" + } + b, err := os.ReadFile(path) + if err != nil { + return "" + } + return string(b) +} + +func readTime(path string) time.Time { + if path == "" { + return time.Time{} + } + if _, err := os.Stat(path); err != nil { + return time.Time{} + } + b, err := os.ReadFile(path) + if err != nil { + return time.Time{} + } + t, err := time.Parse(http.TimeFormat, string(b)) + if err != nil { + return time.Time{} + } + return t +} + // Download downloads the remote resource into the local path. // // Download caches the remote resource if WithCache or WithCacheDir option is specified. @@ -175,7 +210,7 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result, } if o.cacheDir == "" { - if err := downloadHTTP(ctx, localPath, remote, o.description, o.expectedDigest); err != nil { + if err := downloadHTTP(ctx, localPath, "", "", remote, o.description, o.expectedDigest); err != nil { return nil, err } res := &Result{ @@ -187,6 +222,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result, shad := cacheDirectoryPath(o.cacheDir, remote) shadData := filepath.Join(shad, "data") + shadTime := filepath.Join(shad, "time") + shadType := filepath.Join(shad, "type") shadDigest, err := cacheDigestPath(shad, o.expectedDigest) if err != nil { return nil, err @@ -210,6 +247,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result, res := &Result{ Status: StatusUsedCache, CachePath: shadData, + LastModified: readTime(shadTime), + ContentType: readFile(shadType), ValidatedDigest: o.expectedDigest != "", } return res, nil @@ -224,7 +263,7 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result, if err := os.WriteFile(shadURL, []byte(remote), 0o644); err != nil { return nil, err } - if err := downloadHTTP(ctx, shadData, remote, o.description, o.expectedDigest); err != nil { + if err := downloadHTTP(ctx, shadData, shadTime, shadType, remote, o.description, o.expectedDigest); err != nil { return nil, err } // no need to pass the digest to copyLocal(), as we already verified the digest @@ -239,6 +278,8 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result, res := &Result{ Status: StatusDownloaded, CachePath: shadData, + LastModified: readTime(shadTime), + ContentType: readFile(shadType), ValidatedDigest: o.expectedDigest != "", } return res, nil @@ -266,6 +307,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) { shad := cacheDirectoryPath(o.cacheDir, remote) shadData := filepath.Join(shad, "data") + shadTime := filepath.Join(shad, "time") + shadType := filepath.Join(shad, "type") shadDigest, err := cacheDigestPath(shad, o.expectedDigest) if err != nil { return nil, err @@ -285,6 +328,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) { res := &Result{ Status: StatusUsedCache, CachePath: shadData, + LastModified: readTime(shadTime), + ContentType: readFile(shadType), ValidatedDigest: o.expectedDigest != "", } return res, nil @@ -293,6 +338,8 @@ func Cached(remote string, opts ...Opt) (*Result, error) { // cacheDirectoryPath returns the cache subdirectory path. // - "url" file contains the url // - "data" file contains the data +// - "time" file contains the time (Last-Modified header) +// - "type" file contains the type (Content-Type header) func cacheDirectoryPath(cacheDir, remote string) string { return filepath.Join(cacheDir, "download", "by-url-sha256", fmt.Sprintf("%x", sha256.Sum256([]byte(remote)))) } @@ -470,7 +517,7 @@ func validateLocalFileDigest(localPath string, expectedDigest digest.Digest) err return nil } -func downloadHTTP(ctx context.Context, localPath, url, description string, expectedDigest digest.Digest) error { +func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url, description string, expectedDigest digest.Digest) error { if localPath == "" { return fmt.Errorf("downloadHTTP: got empty localPath") } @@ -489,6 +536,18 @@ func downloadHTTP(ctx context.Context, localPath, url, description string, expec if err != nil { return err } + if lastModified != "" { + lm := resp.Header.Get("Last-Modified") + if err := os.WriteFile(lastModified, []byte(lm), 0o644); err != nil { + return err + } + } + if contentType != "" { + ct := resp.Header.Get("Content-Type") + if err := os.WriteFile(contentType, []byte(ct), 0o644); err != nil { + return err + } + } defer resp.Body.Close() bar, err := progressbar.New(resp.ContentLength) if err != nil { diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go index e5de0d65071..d2111200a61 100644 --- a/pkg/downloader/downloader_test.go +++ b/pkg/downloader/downloader_test.go @@ -10,6 +10,7 @@ import ( "runtime" "strings" "testing" + "time" "github.com/opencontainers/go-digest" "gotest.tools/v3/assert" @@ -25,6 +26,8 @@ func TestDownloadRemote(t *testing.T) { t.Cleanup(ts.Close) dummyRemoteFileURL := ts.URL + "/downloader.txt" const dummyRemoteFileDigest = "sha256:380481d26f897403368be7cb86ca03a4bc14b125bfaf2b93bff809a5a2ad717e" + dummyRemoteFileStat, err := os.Stat(filepath.Join("testdata", "downloader.txt")) + assert.NilError(t, err) t.Run("without cache", func(t *testing.T) { t.Run("without digest", func(t *testing.T) { @@ -105,6 +108,17 @@ func TestDownloadRemote(t *testing.T) { _, err = Cached(dummyRemoteFileURL, WithExpectedDigest(wrongDigest), WithCacheDir(cacheDir)) assert.ErrorContains(t, err, "expected digest") }) + t.Run("metadata", func(t *testing.T) { + _, err := Cached(dummyRemoteFileURL, WithExpectedDigest(dummyRemoteFileDigest)) + assert.ErrorContains(t, err, "cache directory to be specified") + + cacheDir := filepath.Join(t.TempDir(), "cache") + r, err := Download(context.Background(), "", dummyRemoteFileURL, WithExpectedDigest(dummyRemoteFileDigest), WithCacheDir(cacheDir)) + assert.NilError(t, err) + assert.Equal(t, StatusDownloaded, r.Status) + assert.Equal(t, dummyRemoteFileStat.ModTime().Truncate(time.Second).UTC(), r.LastModified) + assert.Equal(t, "text/plain; charset=utf-8", r.ContentType) + }) } func TestDownloadLocal(t *testing.T) { diff --git a/pkg/downloader/fuzz_test.go b/pkg/downloader/fuzz_test.go new file mode 100644 index 00000000000..75ad2ffea55 --- /dev/null +++ b/pkg/downloader/fuzz_test.go @@ -0,0 +1,35 @@ +package downloader + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/opencontainers/go-digest" +) + +var a = digest.Algorithm("sha256") + +func FuzzDownload(f *testing.F) { + f.Fuzz(func(t *testing.T, fileContents []byte, checkDigest bool) { + localFile := filepath.Join(t.TempDir(), "localFile") + remoteFile := filepath.Join(t.TempDir(), "remoteFile") + err := os.WriteFile(remoteFile, fileContents, 0o600) + if err != nil { + t.Fatal(err) + } + testLocalFileURL := "file://" + remoteFile + if checkDigest { + d := a.FromBytes(fileContents) + _, _ = Download(context.Background(), + localFile, + testLocalFileURL, + WithExpectedDigest(d)) + } else { + _, _ = Download(context.Background(), + localFile, + testLocalFileURL) + } + }) +} diff --git a/pkg/executil/command.go b/pkg/executil/command.go index e67776e3904..8290f8a9a78 100644 --- a/pkg/executil/command.go +++ b/pkg/executil/command.go @@ -10,15 +10,13 @@ import ( ) type options struct { - ctx *context.Context + ctx context.Context } type Opt func(*options) error // WithContext runs the command with CommandContext. -// -//nolint:gocritic // consider `ctx' to be of non-pointer type -func WithContext(ctx *context.Context) Opt { +func WithContext(ctx context.Context) Opt { return func(o *options) error { o.ctx = ctx return nil @@ -35,7 +33,7 @@ func RunUTF16leCommand(args []string, opts ...Opt) (string, error) { var cmd *exec.Cmd if o.ctx != nil { - cmd = exec.CommandContext(*o.ctx, args[0], args[1:]...) + cmd = exec.CommandContext(o.ctx, args[0], args[1:]...) } else { cmd = exec.Command(args[0], args[1:]...) } diff --git a/pkg/guestagent/procnettcp/fuzz_test.go b/pkg/guestagent/procnettcp/fuzz_test.go new file mode 100644 index 00000000000..97d1130d37f --- /dev/null +++ b/pkg/guestagent/procnettcp/fuzz_test.go @@ -0,0 +1,18 @@ +package procnettcp + +import ( + "bytes" + "testing" +) + +func FuzzParse(f *testing.F) { + f.Fuzz(func(_ *testing.T, data []byte, tcp6 bool) { + var kind Kind + if tcp6 { + kind = TCP6 + } else { + kind = TCP + } + _, _ = Parse(bytes.NewReader(data), kind) + }) +} diff --git a/pkg/hostagent/events/watcher.go b/pkg/hostagent/events/watcher.go index 08072c57d5c..24355dce7f8 100644 --- a/pkg/hostagent/events/watcher.go +++ b/pkg/hostagent/events/watcher.go @@ -44,6 +44,9 @@ loop: case <-ctx.Done(): break loop case line := <-haStdoutTail.Lines: + if line == nil { + break loop + } if line.Err != nil { logrus.Error(line.Err) } diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go index 8745c6349e6..952e1e6d818 100644 --- a/pkg/hostagent/requirements.go +++ b/pkg/hostagent/requirements.go @@ -108,10 +108,10 @@ A possible workaround is to run "apt-get install sshfs" in the guest. `, }) req = append(req, requirement{ - description: "/etc/fuse.conf (/etc/fuse3.conf) to contain \"user_allow_other\"", + description: "fuse to \"allow_other\" as user", script: `#!/bin/bash set -eux -o pipefail -if ! timeout 30s bash -c "until grep -q ^user_allow_other /etc/fuse*.conf; do sleep 3; done"; then +if ! timeout 30s bash -c "until sudo grep -q ^user_allow_other /etc/fuse*.conf; do sleep 3; done"; then echo >&2 "/etc/fuse.conf (/etc/fuse3.conf) is not updated to contain \"user_allow_other\"" exit 1 fi diff --git a/pkg/iso9660util/fuzz_test.go b/pkg/iso9660util/fuzz_test.go new file mode 100644 index 00000000000..27f99d343d3 --- /dev/null +++ b/pkg/iso9660util/fuzz_test.go @@ -0,0 +1,19 @@ +package iso9660util + +import ( + "os" + "path/filepath" + "testing" +) + +func FuzzIsISO9660(f *testing.F) { + f.Fuzz(func(t *testing.T, fileContents []byte) { + imageFile := filepath.Join(t.TempDir(), "fuzz.iso") + err := os.WriteFile(imageFile, fileContents, 0o600) + if err != nil { + t.Fatal(err) + } + //nolint:errcheck // The test doesn't check the return value + IsISO9660(imageFile) + }) +} diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go index 1a2e5ce5a00..a6761b6cf4d 100644 --- a/pkg/limayaml/limayaml.go +++ b/pkg/limayaml/limayaml.go @@ -72,8 +72,8 @@ const ( ) type Rosetta struct { - Enabled *bool `yaml:"enabled" json:"enabled"` - BinFmt *bool `yaml:"binfmt" json:"binfmt"` + Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` + BinFmt *bool `yaml:"binfmt,omitempty" json:"binfmt,omitempty"` } type File struct { diff --git a/pkg/limayaml/limayaml_test.go b/pkg/limayaml/limayaml_test.go new file mode 100644 index 00000000000..b75e9e0b0b5 --- /dev/null +++ b/pkg/limayaml/limayaml_test.go @@ -0,0 +1,43 @@ +package limayaml + +import ( + "encoding/json" + "os" + "testing" + + "gotest.tools/v3/assert" +) + +func dumpJSON(t *testing.T, d interface{}) string { + b, err := json.Marshal(d) + if err != nil { + t.Fatal(err) + } + return string(b) +} + +const emptyYAML = "images: []\n" + +func TestEmptyYAML(t *testing.T) { + var y LimaYAML + t.Log(dumpJSON(t, y)) + b, err := marshalYAML(y) + assert.NilError(t, err) + assert.Equal(t, string(b), emptyYAML) +} + +const defaultYAML = "images: []\n" + +func TestDefaultYAML(t *testing.T) { + bytes, err := os.ReadFile("default.yaml") + assert.NilError(t, err) + var y LimaYAML + err = unmarshalYAML(bytes, &y, "") + assert.NilError(t, err) + y.Images = nil // remove default images + y.Mounts = nil // remove default mounts + t.Log(dumpJSON(t, y)) + b, err := marshalYAML(y) + assert.NilError(t, err) + assert.Equal(t, string(b), defaultYAML) +} diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index a8f67c8e360..6e398a59af5 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -314,7 +314,8 @@ func validateNetwork(y *LimaYAML) error { interfaceName := make(map[string]int) for i, nw := range y.Networks { field := fmt.Sprintf("networks[%d]", i) - if nw.Lima != "" { + switch { + case nw.Lima != "": config, err := networks.Config() if err != nil { return err @@ -335,7 +336,7 @@ func validateNetwork(y *LimaYAML) error { if nw.VZNAT != nil && *nw.VZNAT { return fmt.Errorf("field `%s.lima` and field `%s.vzNAT` are mutually exclusive", field, field) } - } else if nw.Socket != "" { + case nw.Socket != "": if nw.VZNAT != nil && *nw.VZNAT { return fmt.Errorf("field `%s.socket` and field `%s.vzNAT` are mutually exclusive", field, field) } @@ -344,7 +345,7 @@ func validateNetwork(y *LimaYAML) error { } else if err == nil && fi.Mode()&os.ModeSocket == 0 { return fmt.Errorf("field `%s.socket` %q points to a non-socket file", field, nw.Socket) } - } else if nw.VZNAT != nil && *nw.VZNAT { + case nw.VZNAT != nil && *nw.VZNAT: if y.VMType == nil || *y.VMType != VZ { return fmt.Errorf("field `%s.vzNAT` requires `vmType` to be %q", field, VZ) } @@ -354,7 +355,7 @@ func validateNetwork(y *LimaYAML) error { if nw.Socket != "" { return fmt.Errorf("field `%s.vzNAT` and field `%s.socket` are mutually exclusive", field, field) } - } else { + default: return fmt.Errorf("field `%s.lima` or field `%s.socket must be set", field, field) } if nw.MACAddress != "" { diff --git a/pkg/nativeimgutil/fuzz_test.go b/pkg/nativeimgutil/fuzz_test.go new file mode 100644 index 00000000000..02f4aca3d24 --- /dev/null +++ b/pkg/nativeimgutil/fuzz_test.go @@ -0,0 +1,19 @@ +package nativeimgutil + +import ( + "os" + "path/filepath" + "testing" +) + +func FuzzConvertToRaw(f *testing.F) { + f.Fuzz(func(t *testing.T, imgData []byte, withBacking bool, size int64) { + srcPath := filepath.Join(t.TempDir(), "src.img") + destPath := filepath.Join(t.TempDir(), "dest.img") + err := os.WriteFile(srcPath, imgData, 0o600) + if err != nil { + return + } + _ = ConvertToRaw(srcPath, destPath, &size, withBacking) + }) +} diff --git a/pkg/networks/usernet/UDPFileConn.go b/pkg/networks/usernet/udpfileconn.go similarity index 100% rename from pkg/networks/usernet/UDPFileConn.go rename to pkg/networks/usernet/udpfileconn.go diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go index 5fe85332373..43362261b3c 100644 --- a/pkg/qemu/qemu.go +++ b/pkg/qemu/qemu.go @@ -463,6 +463,19 @@ func qemuMachine(arch limayaml.Arch) string { return "virt" } +// audioDevice returns the default audio device. +func audioDevice() string { + switch runtime.GOOS { + case "darwin": + return "coreaudio" + case "linux": + return "pa" // pulseaudio + case "windows": + return "dsound" + } + return "oss" +} + func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err error) { y := cfg.LimaYAML exe, args, err = Exe(*y.Arch) @@ -762,6 +775,9 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er id := "default" // audio device audiodev := *y.Audio.Device + if audiodev == "default" { + audiodev = audioDevice() + } audiodev += fmt.Sprintf(",id=%s", id) args = append(args, "-audiodev", audiodev) // audio controller diff --git a/pkg/store/fuzz_test.go b/pkg/store/fuzz_test.go new file mode 100644 index 00000000000..acce8c902ff --- /dev/null +++ b/pkg/store/fuzz_test.go @@ -0,0 +1,44 @@ +package store + +import ( + "os" + "path/filepath" + "testing" + + "github.com/lima-vm/lima/pkg/store/filenames" +) + +func FuzzLoadYAMLByFilePath(f *testing.F) { + f.Fuzz(func(t *testing.T, fileContents []byte) { + localFile := filepath.Join(t.TempDir(), "yaml_file.yml") + err := os.WriteFile(localFile, fileContents, 0o600) + if err != nil { + t.Fatal(err) + } + //nolint:errcheck // The test doesn't check the return value + LoadYAMLByFilePath(localFile) + }) +} + +func FuzzInspect(f *testing.F) { + f.Fuzz(func(t *testing.T, yml, limaVersion []byte) { + limaDir := t.TempDir() + os.Setenv("LIMA_HOME", limaDir) + err := os.MkdirAll(filepath.Join(limaDir, "fuzz-instance"), 0o700) + if err != nil { + // panic so that we know of problems here + panic(err) + } + ymlFile := filepath.Join(limaDir, "fuzz-instance", filenames.LimaYAML) + limaVersionFile := filepath.Join(limaDir, filenames.LimaVersion) + err = os.WriteFile(ymlFile, yml, 0o600) + if err != nil { + return + } + err = os.WriteFile(limaVersionFile, limaVersion, 0o600) + if err != nil { + return + } + _, _ = Inspect("fuzz-instance") + }) +} diff --git a/pkg/store/instance.go b/pkg/store/instance.go index 79c2e8099f6..6edfc435f05 100644 --- a/pkg/store/instance.go +++ b/pkg/store/instance.go @@ -16,13 +16,13 @@ import ( "text/template" "time" - "github.com/coreos/go-semver/semver" "github.com/docker/go-units" hostagentclient "github.com/lima-vm/lima/pkg/hostagent/api/client" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/textutil" + "github.com/lima-vm/lima/pkg/version/versionutil" "github.com/sirupsen/logrus" ) @@ -174,7 +174,7 @@ func Inspect(instName string) (*Instance, error) { limaVersionFile := filepath.Join(instDir, filenames.LimaVersion) if version, err := os.ReadFile(limaVersionFile); err == nil { inst.LimaVersion = strings.TrimSpace(string(version)) - if _, err = parseLimaVersion(inst.LimaVersion); err != nil { + if _, err = versionutil.Parse(inst.LimaVersion); err != nil { logrus.Warnf("treating lima version %q from %q as very latest release", inst.LimaVersion, limaVersionFile) } } else if !errors.Is(err, os.ErrNotExist) { @@ -192,14 +192,15 @@ func inspectStatusWithPIDFiles(instDir string, inst *Instance, y *limayaml.LimaY } if inst.Status == StatusUnknown { - if inst.HostAgentPID > 0 && inst.DriverPID > 0 { + switch { + case inst.HostAgentPID > 0 && inst.DriverPID > 0: inst.Status = StatusRunning - } else if inst.HostAgentPID == 0 && inst.DriverPID == 0 { + case inst.HostAgentPID == 0 && inst.DriverPID == 0: inst.Status = StatusStopped - } else if inst.HostAgentPID > 0 && inst.DriverPID == 0 { + case inst.HostAgentPID > 0 && inst.DriverPID == 0: inst.Errors = append(inst.Errors, errors.New("host agent is running but driver is not")) inst.Status = StatusBroken - } else { + default: inst.Errors = append(inst.Errors, fmt.Errorf("%s driver is running but host agent is not", inst.VMType)) inst.Status = StatusBroken } @@ -436,36 +437,3 @@ func (inst *Instance) Unprotect() error { inst.Protected = false return nil } - -// parseLimaVersion parses a Lima version string by removing the leading "v" character and -// stripping everything from the first "-" forward (which are `git describe` artifacts and -// not semver pre-release markers). So "v0.19.1-16-gf3dc6ed.m" will be parsed as "0.19.1". -func parseLimaVersion(version string) (*semver.Version, error) { - version = strings.TrimPrefix(version, "v") - version, _, _ = strings.Cut(version, "-") - return semver.NewVersion(version) -} - -// LimaVersionGreaterThan returns true if the Lima version used to create an instance is greater -// than a specific older version. Always returns false if the Lima version is the empty string. -// Unparsable lima versions (like SHA1 commit ids) are treated as the latest version and return true. -// limaVersion is a `github describe` string, not a semantic version. So "0.19.1-16-gf3dc6ed.m" -// will be considered greater than "0.19.1". -func LimaVersionGreaterThan(limaVersion, oldVersion string) bool { - if limaVersion == "" { - return false - } - version, err := parseLimaVersion(limaVersion) - if err != nil { - return true - } - switch version.Compare(*semver.New(oldVersion)) { - case -1: - return false - case +1: - return true - case 0: - return strings.Contains(limaVersion, "-") - } - panic("unreachable") -} diff --git a/pkg/store/instance_test.go b/pkg/store/instance_test.go index 0d532673362..bdbbba7d943 100644 --- a/pkg/store/instance_test.go +++ b/pkg/store/instance_test.go @@ -155,12 +155,3 @@ func TestPrintInstanceTableTwo(t *testing.T) { assert.NilError(t, err) assert.Equal(t, tableTwo, buf.String()) } - -func TestLimaVersionGreaterThan(t *testing.T) { - assert.Equal(t, LimaVersionGreaterThan("", "0.1.0"), false) - assert.Equal(t, LimaVersionGreaterThan("0.0.1", "0.1.0"), false) - assert.Equal(t, LimaVersionGreaterThan("0.1.0", "0.1.0"), false) - assert.Equal(t, LimaVersionGreaterThan("0.1.0-2", "0.1.0"), true) - assert.Equal(t, LimaVersionGreaterThan("0.2.0", "0.1.0"), true) - assert.Equal(t, LimaVersionGreaterThan("abacab", "0.1.0"), true) -} diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go index 60792903f1c..7d5ef24fe8e 100644 --- a/pkg/usrlocalsharelima/usrlocalsharelima.go +++ b/pkg/usrlocalsharelima/usrlocalsharelima.go @@ -55,8 +55,27 @@ func Dir() (string, error) { } else if !errors.Is(err, os.ErrNotExist) { return "", err } + if _, err := os.Stat(gaCandidate + ".gz"); err == nil { + return filepath.Dir(gaCandidate), nil + } else if !errors.Is(err, os.ErrNotExist) { + return "", err + } } return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %q, attempted %v", ostype, arch, self, gaCandidates) } + +func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (string, error) { + if ostype == "" { + return "", errors.New("os must be set") + } + if arch == "" { + return "", errors.New("arch must be set") + } + dir, err := Dir() + if err != nil { + return "", err + } + return filepath.Join(dir, "lima-guestagent."+ostype+"-"+arch), nil +} diff --git a/pkg/version/versionutil/versionutil.go b/pkg/version/versionutil/versionutil.go new file mode 100644 index 00000000000..f7b81a5823b --- /dev/null +++ b/pkg/version/versionutil/versionutil.go @@ -0,0 +1,40 @@ +package versionutil + +import ( + "strings" + + "github.com/coreos/go-semver/semver" +) + +// Parse parses a Lima version string by removing the leading "v" character and +// stripping everything from the first "-" forward (which are `git describe` artifacts and +// not semver pre-release markers). So "v0.19.1-16-gf3dc6ed.m" will be parsed as "0.19.1". +func Parse(version string) (*semver.Version, error) { + version = strings.TrimPrefix(version, "v") + version, _, _ = strings.Cut(version, "-") + return semver.NewVersion(version) +} + +// GreaterThan returns true if the Lima version used to create an instance is greater +// than a specific older version. Always returns false if the Lima version is the empty string. +// Unparsable lima versions (like SHA1 commit ids) are treated as the latest version and return true. +// limaVersion is a `github describe` string, not a semantic version. So "0.19.1-16-gf3dc6ed.m" +// will be considered greater than "0.19.1". +func GreaterThan(limaVersion, oldVersion string) bool { + if limaVersion == "" { + return false + } + version, err := Parse(limaVersion) + if err != nil { + return true + } + switch version.Compare(*semver.New(oldVersion)) { + case -1: + return false + case +1: + return true + case 0: + return strings.Contains(limaVersion, "-") + } + panic("unreachable") +} diff --git a/pkg/version/versionutil/versionutil_test.go b/pkg/version/versionutil/versionutil_test.go new file mode 100644 index 00000000000..410a8848353 --- /dev/null +++ b/pkg/version/versionutil/versionutil_test.go @@ -0,0 +1,16 @@ +package versionutil + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestGreaterThan(t *testing.T) { + assert.Equal(t, GreaterThan("", "0.1.0"), false) + assert.Equal(t, GreaterThan("0.0.1", "0.1.0"), false) + assert.Equal(t, GreaterThan("0.1.0", "0.1.0"), false) + assert.Equal(t, GreaterThan("0.1.0-2", "0.1.0"), true) + assert.Equal(t, GreaterThan("0.2.0", "0.1.0"), true) + assert.Equal(t, GreaterThan("abacab", "0.1.0"), true) +} diff --git a/pkg/vz/rosetta_directory_share_arm64.go b/pkg/vz/rosetta_directory_share_arm64.go index d9db9824809..4d8a173ec04 100644 --- a/pkg/vz/rosetta_directory_share_arm64.go +++ b/pkg/vz/rosetta_directory_share_arm64.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/Code-Hex/vz/v3" + "github.com/coreos/go-semver/semver" + "github.com/lima-vm/lima/pkg/osutil" "github.com/sirupsen/logrus" ) @@ -33,6 +35,17 @@ func createRosettaDirectoryShareConfiguration() (*vz.VirtioFileSystemDeviceConfi if err != nil { return nil, fmt.Errorf("failed to create a new rosetta directory share: %w", err) } + macOSProductVersion, err := osutil.ProductVersion() + if err != nil { + return nil, fmt.Errorf("failed to get macOS product version: %w", err) + } + if !macOSProductVersion.LessThan(*semver.New("14.0.0")) { + cachingOption, err := vz.NewLinuxRosettaAbstractSocketCachingOptions("rosetta") + if err != nil { + return nil, fmt.Errorf("failed to create a new rosetta directory share caching option: %w", err) + } + rosettaShare.SetOptions(cachingOption) + } config.SetDirectoryShare(rosettaShare) return config, nil diff --git a/pkg/vz/vm_darwin.go b/pkg/vz/vm_darwin.go index 2c09c6bec4c..74151f36711 100644 --- a/pkg/vz/vm_darwin.go +++ b/pkg/vz/vm_darwin.go @@ -574,7 +574,8 @@ func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineCo } func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfiguration) error { - if *driver.Yaml.Audio.Device == "vz" { + switch *driver.Yaml.Audio.Device { + case "vz", "default": outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration() if err != nil { return err @@ -587,8 +588,12 @@ func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfigurati config.SetAudioDevicesVirtualMachineConfiguration([]vz.AudioDeviceConfiguration{ soundDeviceConfiguration, }) + return nil + case "", "none": + return nil + default: + return fmt.Errorf("unexpected audio device %q", *driver.Yaml.Audio.Device) } - return nil } func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { diff --git a/pkg/vz/vz_driver_darwin.go b/pkg/vz/vz_driver_darwin.go index a92fe86744a..bf859eea246 100644 --- a/pkg/vz/vz_driver_darwin.go +++ b/pkg/vz/vz_driver_darwin.go @@ -131,9 +131,11 @@ func (l *LimaVzDriver) Validate() error { } } - audioDevice := *l.Yaml.Audio.Device - if audioDevice != "" && audioDevice != "vz" { - logrus.Warnf("field `audio.device` must be %q for VZ driver , got %q", "vz", audioDevice) + switch audioDevice := *l.Yaml.Audio.Device; audioDevice { + case "": + case "vz", "default", "none": + default: + logrus.Warnf("field `audio.device` must be \"vz\", \"default\", or \"none\" for VZ driver, got %q", audioDevice) } switch videoDisplay := *l.Yaml.Video.Display; videoDisplay { diff --git a/pkg/wsl2/vm_windows.go b/pkg/wsl2/vm_windows.go index 664a80c6d08..4533a534fdc 100644 --- a/pkg/wsl2/vm_windows.go +++ b/pkg/wsl2/vm_windows.go @@ -23,7 +23,7 @@ func startVM(ctx context.Context, distroName string) error { "wsl.exe", "--distribution", distroName, - }, executil.WithContext(&ctx)) + }, executil.WithContext(ctx)) if err != nil { return fmt.Errorf("failed to run `wsl.exe --distribution %s`: %w (out=%q)", distroName, err, string(out)) @@ -41,7 +41,7 @@ func initVM(ctx context.Context, instanceDir, distroName string) error { distroName, instanceDir, baseDisk, - }, executil.WithContext(&ctx)) + }, executil.WithContext(ctx)) if err != nil { return fmt.Errorf("failed to run `wsl.exe --import %s %s %s`: %w (out=%q)", distroName, instanceDir, baseDisk, err, string(out)) @@ -55,7 +55,7 @@ func stopVM(ctx context.Context, distroName string) error { "wsl.exe", "--terminate", distroName, - }, executil.WithContext(&ctx)) + }, executil.WithContext(ctx)) if err != nil { return fmt.Errorf("failed to run `wsl.exe --terminate %s`: %w (out=%q)", distroName, err, string(out)) @@ -164,7 +164,7 @@ func unregisterVM(ctx context.Context, distroName string) error { "wsl.exe", "--unregister", distroName, - }, executil.WithContext(&ctx)) + }, executil.WithContext(ctx)) if err != nil { return fmt.Errorf("failed to run `wsl.exe --unregister %s`: %w (out=%q)", distroName, err, string(out)) diff --git a/pkg/yqutil/fuzz_test.go b/pkg/yqutil/fuzz_test.go new file mode 100644 index 00000000000..0ac0632192b --- /dev/null +++ b/pkg/yqutil/fuzz_test.go @@ -0,0 +1,11 @@ +package yqutil + +import ( + "testing" +) + +func FuzzEvaluateExpression(f *testing.F) { + f.Fuzz(func(_ *testing.T, expression string, content []byte) { + _, _ = EvaluateExpression(expression, content) + }) +} diff --git a/website/content/en/docs/Community/_index.md b/website/content/en/docs/community/_index.md similarity index 100% rename from website/content/en/docs/Community/_index.md rename to website/content/en/docs/community/_index.md diff --git a/website/content/en/docs/Community/Contributing/_index.md b/website/content/en/docs/community/contributing/_index.md similarity index 100% rename from website/content/en/docs/Community/Contributing/_index.md rename to website/content/en/docs/community/contributing/_index.md diff --git a/website/content/en/docs/Community/Governance/_index.md b/website/content/en/docs/community/governance/_index.md similarity index 100% rename from website/content/en/docs/Community/Governance/_index.md rename to website/content/en/docs/community/governance/_index.md diff --git a/website/content/en/docs/Community/Roadmap/_index.md b/website/content/en/docs/community/roadmap/_index.md similarity index 100% rename from website/content/en/docs/Community/Roadmap/_index.md rename to website/content/en/docs/community/roadmap/_index.md diff --git a/website/content/en/docs/Community/Subprojects/_index.md b/website/content/en/docs/community/subprojects/_index.md similarity index 100% rename from website/content/en/docs/Community/Subprojects/_index.md rename to website/content/en/docs/community/subprojects/_index.md diff --git a/website/content/en/docs/Config/_index.md b/website/content/en/docs/config/_index.md similarity index 78% rename from website/content/en/docs/Config/_index.md rename to website/content/en/docs/config/_index.md index bb876ea6e4c..01f831fe57c 100644 --- a/website/content/en/docs/Config/_index.md +++ b/website/content/en/docs/config/_index.md @@ -12,3 +12,5 @@ The current default spec: - Disk: 100 GiB - Mounts: `~` (read-only), `/tmp/lima` (writable) - SSH: 127.0.0.1:60022 + +For environment variables, see [Environment Variables](./environment-variables/). diff --git a/website/content/en/docs/config/environment-variables.md b/website/content/en/docs/config/environment-variables.md new file mode 100644 index 00000000000..563b2274dbc --- /dev/null +++ b/website/content/en/docs/config/environment-variables.md @@ -0,0 +1,62 @@ +--- +title: Environment Variables +weight: 6 +--- + +## Environment Variables + +This page documents the environment variables used in Lima. + +### `LIMA_INSTANCE` + +- **Description**: Specifies the name of the Lima instance to use. +- **Default**: `default` +- **Usage**: + ```sh + export LIMA_INSTANCE=my-instance + lima uname -a + ``` +- **Used in**: `cmd/lima`, `cmd/lima.bat`, `cmd/docker.lima`, `cmd/kubectl.lima` + +### `LIMA_SHELL` + +- **Description**: Specifies the shell interpreter to use inside the Lima instance. +- **Default**: User's shell configured inside the instance +- **Usage**: + ```sh + export LIMA_SHELL=/bin/bash + lima + ``` +- **Used in**: `cmd/lima`, `cmd/limactl/shell.go` + +### `LIMA_WORKDIR` + +- **Description**: Specifies the initial working directory inside the Lima instance. +- **Default**: Current directory from the host +- **Usage**: + ```sh + export LIMA_WORKDIR=/home/user/project + lima + ``` +- **Used in**: `cmd/lima`, `cmd/limactl/shell.go` + +### `LIMACTL` + +- **Description**: Specifies the path to the `limactl` binary. +- **Default**: `limactl` in `$PATH` +- **Usage**: + ```sh + export LIMACTL=/usr/local/bin/limactl + lima + ``` +- **Used in**: `cmd/lima`, `cmd/lima.bat` + +### `LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT` + +- **Description**: Specifies the timeout duration for resolving the IP address in usernet. +- **Default**: 2 minutes +- **Usage**: + ```sh + export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5 + ``` +- **Used in**: `cmd/limactl/usernet.go` diff --git a/website/content/en/docs/Config/Mount/_index.md b/website/content/en/docs/config/mount/_index.md similarity index 100% rename from website/content/en/docs/Config/Mount/_index.md rename to website/content/en/docs/config/mount/_index.md diff --git a/website/content/en/docs/Config/Multi-arch/_index.md b/website/content/en/docs/config/multi-arch/_index.md similarity index 100% rename from website/content/en/docs/Config/Multi-arch/_index.md rename to website/content/en/docs/config/multi-arch/_index.md diff --git a/website/content/en/docs/Config/Network/_index.md b/website/content/en/docs/config/network/_index.md similarity index 100% rename from website/content/en/docs/Config/Network/_index.md rename to website/content/en/docs/config/network/_index.md diff --git a/website/content/en/docs/Config/VMType/_index.md b/website/content/en/docs/config/vmtype/_index.md similarity index 100% rename from website/content/en/docs/Config/VMType/_index.md rename to website/content/en/docs/config/vmtype/_index.md diff --git a/website/content/en/docs/dev/Internals/_index.md b/website/content/en/docs/dev/internals/_index.md similarity index 100% rename from website/content/en/docs/dev/Internals/_index.md rename to website/content/en/docs/dev/internals/_index.md diff --git a/website/content/en/docs/dev/Testing/_index.md b/website/content/en/docs/dev/testing/_index.md similarity index 100% rename from website/content/en/docs/dev/Testing/_index.md rename to website/content/en/docs/dev/testing/_index.md diff --git a/website/content/en/docs/Examples/_index.md b/website/content/en/docs/examples/_index.md similarity index 100% rename from website/content/en/docs/Examples/_index.md rename to website/content/en/docs/examples/_index.md diff --git a/website/content/en/docs/Installation/_index.md b/website/content/en/docs/installation/_index.md similarity index 100% rename from website/content/en/docs/Installation/_index.md rename to website/content/en/docs/installation/_index.md diff --git a/website/content/en/docs/Reference/_index.md b/website/content/en/docs/reference/_index.md similarity index 100% rename from website/content/en/docs/Reference/_index.md rename to website/content/en/docs/reference/_index.md diff --git a/website/content/en/docs/Releases/_index.md b/website/content/en/docs/releases/_index.md similarity index 100% rename from website/content/en/docs/Releases/_index.md rename to website/content/en/docs/releases/_index.md diff --git a/website/content/en/docs/Releases/Deprecated/_index.md b/website/content/en/docs/releases/deprecated/_index.md similarity index 100% rename from website/content/en/docs/Releases/Deprecated/_index.md rename to website/content/en/docs/releases/deprecated/_index.md diff --git a/website/content/en/docs/Releases/Experimental/_index.md b/website/content/en/docs/releases/experimental/_index.md similarity index 100% rename from website/content/en/docs/Releases/Experimental/_index.md rename to website/content/en/docs/releases/experimental/_index.md diff --git a/website/content/en/docs/Talks/_index.md b/website/content/en/docs/talks/_index.md similarity index 100% rename from website/content/en/docs/Talks/_index.md rename to website/content/en/docs/talks/_index.md diff --git a/website/content/en/docs/Templates/_index.md b/website/content/en/docs/templates/_index.md similarity index 100% rename from website/content/en/docs/Templates/_index.md rename to website/content/en/docs/templates/_index.md diff --git a/website/content/en/docs/Usage/_index.md b/website/content/en/docs/usage/_index.md similarity index 100% rename from website/content/en/docs/Usage/_index.md rename to website/content/en/docs/usage/_index.md diff --git a/website/layouts/404.html b/website/layouts/404.html index 1a9bd70440a..d32828c8b2f 100644 --- a/website/layouts/404.html +++ b/website/layouts/404.html @@ -2,6 +2,5 @@

Not found

Oops! This page doesn't exist. Try going back to the home page.

-

You can learn how to make a 404 page like this in Custom 404 Pages.

{{- end }}