lima-vm/lima · error

failed to create /etc/sudoers.d directory

Error message

failed to create /etc/sudoers.d directory: %w

What it means

os.MkdirAll("/etc/sudoers.d", 0o700) failed while preparing to append the user's sudoers entry. On a healthy macOS guest this directory normally exists; failure indicates the root filesystem is not writable by the agent or an unusual system state. The user's sudo setup is aborted.

Solutions

  1. Ensure the Lima guestagent runs as root inside the VM
  2. Check the root volume is writable (`touch /etc/test`)
  3. Verify /etc/sudoers.d is a directory, not a regular file; fix or recreate the instance
  4. Free disk space if the wrapped error is ENOSPC

Example fix

// inside the VM (recovery)
sudo rm -f /etc/sudoers.d        # only if it is a regular file
sudo mkdir -p /etc/sudoers.d && sudo chmod 700 /etc/sudoers.d
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Lstat("/etc/sudoers.d"); err == nil && !fi.IsDir() {
    return errors.New("/etc/sudoers.d exists but is not a directory")
}
if err := os.MkdirAll("/etc/sudoers.d", 0o700); err != nil {
    return fmt.Errorf("/etc/sudoers.d not preparable: %w", err)
}

Try / catch

if err := processUserData(ctx, data); err != nil {
    var pe *os.PathError
    if errors.As(err, &pe) && strings.Contains(err.Error(), "sudoers.d directory") {
        log.Printf("cannot prepare %s: %v — check root privileges and writable /etc", pe.Path, pe.Err)
    }
}

Prevention

When it happens

Trigger: writeSudoers runs (user has a `sudo` field) and creating /etc/sudoers.d fails: read-only root volume, EPERM because the guestagent is not root, or /etc/sudoers.d exists as a regular file.

Common situations: Running the guestagent unprivileged in custom setups; sealed/system read-only volume configurations; a previous provisioning step replaced /etc/sudoers.d with a file; full disk (ENOSPC).

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/ce3fd2af8138a75e. Report an issue: GitHub.

Appendix: source

Thrown at pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go:325

			return fmt.Errorf("failed to chown %#q for user %#q: %w", f, u.Name, err)
		}
	}
	if u.Sudo != "" {
		if err := writeSudoers(u.Name, u.Sudo); err != nil {
			return fmt.Errorf("failed to write sudoers file for user %#q: %w", u.Name, err)
		}
	}
	return nil
}

// writeSudoers appends a sudoers entry for the given user.
// writeSudoers is expected be called only once on creating the user account.
func writeSudoers(userName, sudo string) error {
	if strings.Contains(sudo, "\n") {
		return errors.New("sudo field must not contain newline characters")
	}
	if err := os.MkdirAll("/etc/sudoers.d", 0o700); err != nil {
		return fmt.Errorf("failed to create /etc/sudoers.d directory: %w", err)
	}
	sudoersPath := "/etc/sudoers.d/90-cloud-init-users"
	f, err := os.OpenFile(sudoersPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o400)
	if err != nil {
		return fmt.Errorf("failed to open sudoers file %#q: %w", sudoersPath, err)
	}
	if _, err = fmt.Fprintf(f, "%s %s\n", userName, sudo); err != nil {
		_ = f.Close()
		return fmt.Errorf("failed to write to sudoers file %#q for user %#q: %w", sudoersPath, userName, err)
	}
	return f.Close()
}

func writeFiles(ctx context.Context, entry cloudinittypes.WriteFile) error {
	if entry.Path == "" {
		return errors.New("path is required for write_files entry")
	}
	perm := os.FileMode(0o644)

View on GitHub (pinned to dd909d0973)