当前位置: 首页 > news >正文

折腾笔记[62]-macOS使用linsk挂载lvm逻辑卷的ext4分区

摘要

本文记录如何在 Apple Silicon Mac 上通过修改 Linsk 实现离线编译与运行,并在物理磁盘上创建 LVM 逻辑卷及 ext4 分区,最终通过 FTP 网络共享在 macOS 上读写访问。核心目标是将 Apple Silicon Mac mini 作为可以随意扩展磁盘的性能强大的 NAS,解决 macOS 原生不支持 LVM 和 ext4 的问题。

声明

本文人类为第一作者, 龙虾为通讯作者. 本文有AI生成内容.

工程仓库

  • 上游仓库: [https://github.com/AlexSSD7/linsk]
  • 修改后仓库: [https://github.com/qsbye/linsk-plus], Release 中有编译完成的可执行文件.

简介

LVM 逻辑卷管理简介

LVM(Logical Volume Manager,逻辑卷管理器)是 Linux 内核提供的磁盘管理抽象层,它允许在物理磁盘之上创建灵活的存储池(Volume Group),并从中划分出可动态调整大小的逻辑卷(Logical Volume)。相比传统分区表,LVM 的核心优势在于:

  • 动态扩容/缩容:无需重新分区即可调整逻辑卷大小
  • 跨物理磁盘聚合:可将多块物理磁盘合并为一个卷组
  • 快照支持:可创建逻辑卷的时间点快照
  • 任意添加物理磁盘和扩容分区:新磁盘加入卷组后,现有逻辑卷可直接扩展

Linsk 简介

[https://github.com/AlexSSD7/linsk]

Linsk 是一个非常务实的开源方案,它证明了"VM 路线"是在 macOS 上支持 LVM 的最务实方案——它完全规避了重写 Linux 内核子系统的巨大工程成本。Linsk 不重新实现任何文件系统或 LVM 逻辑,而是走了一条"借力打力"的路线:在 macOS 上启动一个轻量级 Alpine Linux VM(约 130MB),通过硬件虚拟化(HVF)让 VM 直接访问物理磁盘,然后在 VM 内用原生 Linux 工具(lvm2、cryptsetup、mount)完成所有复杂工作,最后通过 SMB/AFP/FTP 网络共享将文件暴露给 macOS Finder。

核心定位

  • 零文件系统实现成本:复用 Linux 内核成熟的 LVM2、ext4、LUKS 等子系统
  • 硬件虚拟化直通:通过 QEMU HVF 加速,VM 直接访问物理块设备
  • 网络共享前端:支持 SMB(macOS 原生)、AFP(旧版 macOS)、FTP(跨平台)三种协议
  • 单二进制分发:Go 编写的 CLI 工具,编译后仅一个可执行文件

源码结构

Linsk 采用 Go 模块结构,源码总计约 5000 行 Go 代码,分布于 30 余个源文件中:

linsk/
├── cmd/           # Cobra CLI 命令定义 (build, run, shell, ls, clean)
├── constants/     # 常量定义 (镜像版本、BIOS 路径等)
├── imgbuilder/    # VM 镜像构建逻辑
├── nettap/        # macOS TAP 网络接口分配
├── osspecifics/   # 跨平台 OS 检查 (darwin/linux/windows)
├── qemucli/       # QEMU 命令行参数构造器
├── share/         # 网络共享后端 (SMB/AFP/FTP)
├── sshutil/       # SSH 连接工具
├── storage/       # 文件下载/缓存/嵌入资源管理
├── utils/         # 通用工具函数
├── vm/            # VM 生命周期管理 (启动、SSH、挂载、共享)
└── main.go        # 入口

关键设计模式

  1. VM 即服务:Alpine VM 作为临时 Linux 环境,用完即销毁,无状态设计
  2. 设备直通:通过 QEMU -device virtio-blk-pci 将物理磁盘直通给 VM
  3. SSH 桥接:VM 启动后通过本地端口转发建立 SSH 通道,所有操作通过 SSH 执行
  4. 网络共享抽象share.Backend 接口统一封装 SMB/AFP/FTP 三种协议

技术亮点

  • 轻量级 VM:Alpine Linux 最小化镜像,启动时间 < 30 秒
  • 离线运行:修改后支持将 ISO、BIOS、APK 缓存嵌入二进制,无需网络
  • 安全设计:默认禁用 VM 网络访问,磁盘直通需显式指定
  • 密码固定:共享密码固定为 linsk,便于自动化脚本和开机自启

工程

1. 嵌入资源文件以支持离线运行

Linsk 原版在首次运行时需要从网络下载 Alpine ISO 镜像和 EDK2 UEFI BIOS 文件。为适配离线机器,我们将这些资源嵌入到 Go 二进制中,运行时提取到数据目录。

assets/embed.go

package assetsimport ("embed""fmt""io""os""path/filepath""github.com/AlexSSD7/linsk/utils"
)//go:embed files/*.img files/*.fd files/apks/*.apk
var embeddedFiles embed.FS// ExtractFile extracts an embedded file to the specified destination.
func ExtractFile(name, dst string, size int64) error {src, err := embeddedFiles.Open("files/" + name)if err != nil {return fmt.Errorf("open embedded file %v: %w", name, err)}defer src.Close()if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {return fmt.Errorf("create dir for %v: %w", dst, err)}dstFile, err := os.Create(dst)if err != nil {return fmt.Errorf("create dst file %v: %w", dst, err)}defer dstFile.Close()_, err = io.Copy(dstFile, src)if err != nil {return fmt.Errorf("copy embedded file %v to %v: %w", name, dst, err)}return nil
}

storage/download.go(修改后,从嵌入资源提取而非网络下载)

package storageimport ("fmt""os""path/filepath""github.com/AlexSSD7/linsk/assets""github.com/AlexSSD7/linsk/constants"
)func (s *Storage) downloadFile(name string, size int64) (string, error) {dstPath := filepath.Join(s.dataDir, name)// Check if already extracted.if _, err := os.Stat(dstPath); err == nil {return dstPath, nil}s.log.Info("Extracting embedded file", "to", dstPath, "size", utils.FormatBytes(size))if err := assets.ExtractFile(name, dstPath, size); err != nil {return "", fmt.Errorf("extract embedded file %v: %w", name, err)}info, err := os.Stat(dstPath)if err != nil {return "", fmt.Errorf("stat extracted file: %w", err)}s.log.Info("Successfully extracted embedded file", "to", dstPath, "out-size", utils.FormatBytes(info.Size()))return dstPath, nil
}

资源文件放置目录:

assets/
├── embed.go
└── files/├── alpine-3.20.3-aarch64.img      # Alpine ISO 镜像 (~73MB)├── edk2-aarch64-code.fd           # EDK2 UEFI BIOS (~67MB)└── apks/├── lvm2-*.apk                 # LVM2 工具包├── e2fsprogs-*.apk            # ext4 工具包└── ...                        # 其他依赖 APK

2. 嵌入 APK 包缓存

VM 镜像构建过程中需要安装 lvm2e2fsprogs 等 Alpine 包。原版通过 VM 内的 apk add 从网络下载,离线环境下会失败。我们将 APK 包预下载并嵌入二进制,构建时通过本地仓库安装。

imgbuilder/imgbuilder.go(APK 本地仓库配置片段)

// 在 VM 内配置本地 APK 仓库
cmds := []string{"mkdir -p /etc/apk/cache","cp /media/apks/*.apk /etc/apk/cache/","echo '/etc/apk/cache' >> /etc/apk/repositories","apk add --no-network lvm2 e2fsprogs",
}

3. 修复 FTP 写入权限

原版 Linsk 通过 FTP 共享时,/mnt 目录的权限为 root:root 755,导致 FTP 用户 linsk 无法写入。修改 vm/filemanager.go,在启动共享服务前修复目录权限。

vm/filemanager.go

func (fm *FileManager) fixMountPermissions() error {// Ensure the linsk user can read/write the mount point._, err := fm.RunCmd("chown", []string{"linsk:linsk", "/mnt"}, nil, true)if err != nil {return fmt.Errorf("chown /mnt: %w", err)}_, err = fm.RunCmd("chmod", []string{"775", "/mnt"}, nil, true)if err != nil {return fmt.Errorf("chmod /mnt: %w", err)}return nil
}

StartShare 方法中调用:

func (fm *FileManager) StartShare(backend share.Backend, ports share.Ports) (string, error) {if err := fm.fixMountPermissions(); err != nil {return "", fmt.Errorf("fix mount permissions: %w", err)}// ... 启动共享服务
}

4. 固定共享密码为 "linsk"

原版 Linsk 每次运行生成随机密码,不利于自动化脚本和开机自启。修改 cmd/run.go,将密码固定为 linsk

cmd/run.go

// 原代码:pass, err := sshutil.GenerateRandomPassword(16)
// 修改为固定密码
pass := "linsk"

5. 构建与测试

编译

# 确保资源文件已放置
ls assets/files/
# alpine-3.20.3-aarch64.img  edk2-aarch64-code.fd  apks/# 编译(Go 1.21+)
go build -o linsk .# 或使用 Makefile
make build

构建 VM 镜像

# 首次运行需要构建 VM 镜像
sudo ./linsk build# 如果镜像已存在,使用 --overwrite 覆盖
sudo ./linsk build --overwrite

构建成功输出:

time=2026-08-16T13:58:56.089+08:00 level=INFO msg="VM image built successfully" path=/Users/workspace/.linsk/3.20.3-aarch64-linsk1.qcow2

测试 LVM + ext4

使用 /Volumes/QSANDISK/dev/disk4)作为测试磁盘,数据可抹掉。

# 1. 卸载磁盘(macOS 侧)
sudo diskutil unmountDisk /dev/disk4# 2. 进入 Linsk shell,在 VM 内操作磁盘
sudo ./linsk shell "dev:/dev/disk4"

在 VM 内创建 LVM 逻辑卷和 ext4 分区:

# 查看磁盘
localhost:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
vda    253:0    0    1G  0 disk 
├─vda1 253:1    0  160M  0 part /boot/efi
├─vda2 253:2    0  256M  0 part [SWAP]
└─vda3 253:3    0  606M  0 part /
vdb    253:16   0 28.7G  0 disk 
└─vdb1 253:17   0 28.7G  0 part # 清除原有分区表
localhost:~# wipefs -a /dev/vdb# 创建物理卷
localhost:~# pvcreate /dev/vdbPhysical volume "/dev/vdb" successfully created.# 创建卷组
localhost:~# vgcreate testvg /dev/vdbVolume group "testvg" successfully created# 创建逻辑卷
localhost:~# lvcreate -L 28G -n testlv testvgLogical volume "testlv" created.# 创建 ext4 文件系统
localhost:~# mkfs.ext4 /dev/testvg/testlv
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 7340032 4k blocks and 1835008 inodes
Filesystem UUID: 9157f7f1-d43e-4a30-915b-4313f1345bcf
...# 查看结果
localhost:~# lsblk
NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
vda             253:0    0    1G  0 disk 
├─vda1          253:1    0  160M  0 part /boot/efi
├─vda2          253:2    0  256M  0 part [SWAP]
└─vda3          253:3    0  606M  0 part /
vdb             253:16   0 28.7G  0 disk 
└─testvg-testlv 252:0    0   28G  0 lvm  localhost:~# exit

通过 FTP 共享访问

# 使用 mapper 路径指定逻辑卷
sudo ./linsk run --share-backend ftp "dev:/dev/disk4" mapper/testvg-testlv

启动成功输出:

time=... level=INFO msg="Started the network share successfully" backend=ftp
===========================
[Network File Share Config]
The network file share was started. Please use the credentials below to connect to the file server.Type: FTP
URL: ftp://127.0.0.1:9000
Username: linsk
Password: linsk
===========================

使用 Cyberduck 或 Finder 连接 ftp://127.0.0.1:9000,用户 linsk,密码 linsk,即可读写访问 ext4 分区中的文件。

CyberDuck 通过 FTP 访问 LVM 逻辑卷的 ext4 分区
CyberDuck FTP 访问截图

6. 保留预构建 VM 镜像

为避免每次重新构建 VM 镜像,可将构建好的 qcow2 镜像保留在项目目录:

# 复制预构建镜像到项目目录
sudo cp /Users/workspace/.linsk/3.20.3-aarch64-linsk1.qcow2 .# 后续可直接使用,无需重新 build
sudo ./linsk shell "dev:/dev/disk4"

常见问题

Q: linsk build 报错 "image already exists"

A: 使用 --overwrite 参数强制重建:

sudo ./linsk build --overwrite

Q: QEMU 启动报错 dylib 缺失

A: 确保已安装 QEMU 及其依赖:

brew install qemu

若仍报 libglib-2.0.0.dylib 缺失,尝试:

brew reinstall glib
brew link --overwrite glib

Q: linsk run 挂载报错 "unknown filesystem type 'LVM2_member'"

A: 直接指定块设备(如 /dev/disk4)时,Linsk 尝试自动识别文件系统,但 LVM 物理卷的文件系统类型为 LVM2_member,无法直接挂载。需指定逻辑卷路径:

# 错误:sudo ./linsk run --share-backend ftp "dev:/dev/disk4"
# 正确:指定 mapper 路径
sudo ./linsk run --share-backend ftp "dev:/dev/disk4" mapper/testvg-testlv

Q: FTP 连接后只能读取,无法写入

不要使用Finder, 改为使用Cyberduck才能正确读写

A: 确保使用了修改后的版本(已修复 /mnt 权限)。如仍有问题,手动检查 VM 内权限:

# 在 linsk shell 中
localhost:~# ls -ld /mnt
drwxrwxr-x 2 linsk linsk 4096 ... /mnt

若权限不对,手动修复:

localhost:~# chown linsk:linsk /mnt && chmod 775 /mnt

Q: 如何开机自动启动 FTP 共享

A: 由于密码已固定为 linsk,可创建 macOS LaunchAgent:

<!-- ~/Library/LaunchAgents/com.qsbye.linsk.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict><key>Label</key><string>com.qsbye.linsk</string><key>ProgramArguments</key><array><string>/usr/local/bin/linsk</string><string>run</string><string>--share-backend</string><string>ftp</string><string>dev:/dev/disk4</string><string>mapper/testvg-testlv</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
</dict>
</plist>

加载配置:

launchctl load ~/Library/LaunchAgents/com.qsbye.linsk.plist
http://www.jsqmd.com/news/1405415/

相关文章:

  • 攀枝花市正规防水补漏公司 (2026 新) - 用户198513
  • 2026年冲美院必看 广州美术艺考画室更高画室好在哪? - 招财兔数字员工
  • 武汉洪山区漏水检测维修全城上门 2026 全网口碑优选:防水补漏公司 #武汉 - 超人防水
  • switch-cuda不止bash:在zsh与fish中复用CUDA切换逻辑的进阶技巧
  • 电视盒子播放器总卡顿?这款开源播放器如何一招通吃200+种视频格式
  • macOS鼠标光标自定义完整指南:免费开源工具Mousecape从下载到玩出花样
  • 2026年8月拉萨外墙漏水维修靠谱渠道盘点,高层高空渗水修缮避坑指南 - 聪居到家
  • gibMacOS 实战手册:不碰 Mac 也能直连 Apple 目录,三分钟拉回 macOS 安装文件
  • 实测全网AI论文工具[特殊字符]终于找到零返修的毕业神器|OKBIYE太稳了
  • 2026年无锡健康管理行业观察:细胞科技与传统服务并行发展
  • 2026上海A-Level学校:光剑总量领跑、籽奥效率居首——五强盘点 - 米諾
  • 2026年国内10家核心GEO优化服务商全解析:选型标准、场景适配及合作避坑指南 - 产业观察报
  • 2026中东五金产品外贸出海GEO优化服务商甄选:7家靠谱平台盘点与合作避坑全指南 - 产业观察报
  • 孩子专注力差报什么课:避免套路化教学保护探索好奇心
  • 新疆合规驼奶商家详解:西域之花的全链路服务体系 - 互联网科技品牌测评
  • 河南温县功夫文武学校 2026 招生简章|完整简介、课程、收费、招生热线一览 - Luckyone王
  • 2026 温县功夫文武学校完整招生简章|学校简介、课程班型、升学出路、报名须知梳理 - Luckyone王
  • GTA5线上小助手快速上手指南:解锁全功能,轻松玩转洛圣都
  • 宜昌伍家岗区漏水检测维修全城上门2026全网口碑优选:防水补漏公司#宜昌 - 超人防水
  • 手把手教你用 vue-resume 在线编辑简历:点击即可修改的 10 个实用技巧
  • Driver Store Explorer 驱动清理避坑指南:一次搞懂驱动存储,安全释放 C 盘空间
  • linux之git
  • AJAX Solr与HTML5 History API:ParameterHistoryStore高级状态管理指南
  • 告别打不开的微信语音:silk v3音频解码快速上手,一条命令批量转MP3
  • 温县功夫文武学校 #2026 武校招生简章 #焦作温县文武学校 #河南太极少林文武学校 #河南文武学校招生 - Luckyone王
  • 福建省电大中专最新招生办联系方式 - 升学择校早知道
  • 微信语音转MP3难在哪?silk-v3-decoder让Silk v3音频转换一步到位
  • 国石玉器城:十六载直营新疆和田玉全产业链服务商 - 互联网科技品牌测评
  • Driver Store Explorer 从零到一全攻略:免费清理 Windows 驱动存储,快速释放系统盘空间
  • 从3小时到3分钟:开源FunClip智能视频剪辑工具实战指南