//go:build darwin // +build darwin package main import ( "errors" "fmt" "os" "os/exec" "strings" ) func RuntimeDir() (string, error) { // TODO this could be garbage collected on a 3d basis out, err := exec.Command("getconf", "DARWIN_USER_TEMP_DIR").Output() rundir := strings.TrimRight(string(out[:]), " \t\n") if err != nil { return "", fmt.Errorf("cannot get DARWIN_USER_TEMP_DIR: %v", err) } return strings.TrimSuffix(rundir, "/"), nil } // Does: // mkdir /tmp/mymount // NUMSECTORS=128000 # a sector is 512 bytes // mydev=`hdiutil attach -nomount ram://$NUMSECTORS` // newfs_hfs $mydev // mount -t hfs $mydev /tmp/mymount func MountSecretFs(mountpoint string, keysGID int, _useTmpfs bool, userMode bool) error { if err := os.MkdirAll(mountpoint, 0o751); err != nil { return fmt.Errorf("cannot create directory '%s': %w", mountpoint, err) } if _, err := os.Stat(mountpoint + "/sops-nix-secretfs"); !errors.Is(err, os.ErrNotExist) { return nil // secret fs already exists } // MacOS/darwin options for temporary files: // - /tmp or NSTemporaryDirectory is persistent, and regularly wiped from files not touched >3d // https://wiki.lazarus.freepascal.org/Locating_the_macOS_tmp_directory // - there is no ramfs, also `man statfs` doesn't have flags for memfs things // - we can allocate and mount statically allocated memory (ram://), however most // functions for that are not publicly exposed to userspace. mb := 64 // size in MB size := mb * 1024 * 1024 / 512 // size in sectors a 512 bytes cmd := exec.Command("hdiutil", "attach", "-nomount", fmt.Sprintf("ram://%d", int(size))) out, err := cmd.Output() // /dev/diskN if err != nil { return fmt.Errorf("cannot execute hdiutil: %w", err) } diskpath := strings.TrimRight(string(out[:]), " \t\n") // format as hfs _, err = exec.Command("newfs_hfs", "-s", diskpath).Output() if err != nil { return fmt.Errorf("cannot create hfs filesystem at '%s': %w", diskpath, err) } // "posix" mount takes `struct hfs_mount_args` which we dont have bindings for at hand. // See https://stackoverflow.com/a/49048846/4108673 // err = unix.Mount("hfs", mountpoint, unix.MNT_NOEXEC|unix.MNT_NODEV, mount_args) // Instead we call: _, err = exec.Command("mount", "-t", "hfs", "-o", "nobrowse,nodev,nosuid,-m=0751", diskpath, mountpoint).Output() if err != nil { return fmt.Errorf("cannot mount '%s' to '%s': %w", diskpath, mountpoint, err) } // There is no documented way to check for memfs mountpoint. Thus we place a file. path := mountpoint + "/sops-nix-secretfs" _, err = os.Create(path) if err != nil { return fmt.Errorf("cannot create file '%s': %w", path, err) } // This would be the way to check on unix. //buf := unix.Statfs_t{} //if err := unix.Statfs(mountpoint, &buf); err != nil { // return fmt.Errorf("Cannot get statfs for directory '%s': %w", mountpoint, err) //} // //if int32(buf.Type) != RAMFS_MAGIC { // if err := unix.Mount("none", mountpoint, "ramfs", unix.MS_NODEV|unix.MS_NOSUID, "mode=0751"); err != nil { // return fmt.Errorf("Cannot mount: %s", err) // } //} if !userMode { if err := os.Chown(mountpoint, 0, int(keysGID)); err != nil { return fmt.Errorf("cannot change owner/group of '%s' to 0/%d: %w", mountpoint, keysGID, err) } } return nil }