编辑
2026-08-19
日常工作
00

目录

MigrateNAS 跨品牌NAS硬盘迁移系统
产品需求文档 (PRD)
目录
1. 文档概述
1.1 背景
1.2 文档目的
1.3 术语表
2. 市场分析与用户痛点
2.1 目标用户画像
用户A:家庭NAS升级者
用户B:中小企业IT管理员
用户C:NAS发烧友/技术爱好者
2.2 市场规模
2.3 竞品分析
3. 产品定位与价值主张
3.1 产品愿景
3.2 核心价值主张
3.3 产品形态
4. 技术架构方案
4.1 整体架构
4.2 核心模块详解
4.2.1 SHR逆向兼容引擎
4.2.2 Btrfs子卷映射器
4.2.3 元数据迁移引擎
4.3 部署方案
方案A:Live USB启动盘(个人用户)
方案B:NAS厂商SDK集成(B2B)
5. 核心功能规格
5.1 功能矩阵
5.2 性能规格
5.3 兼容性规格
6. 用户流程设计
6.1 典型使用场景
场景A:个人用户Basic盘迁移
场景B:企业SHR阵列迁移
6.2 命令行界面(CLI)
7. 实施路线图
7.1 开发阶段
7.2 里程碑
7.3 资源需求
8. 风险评估与缓解策略
8.1 技术风险
8.2 商业风险
8.3 安全考虑
9. 商业模式
9.1 收入模式
9.2 定价策略
9.3 市场推广策略
10. 附录
10.1 技术参考资料
10.2 测试设备清单
10.3 常见问题(FAQ)
文档修订历史

MigrateNAS 跨品牌NAS硬盘迁移系统

产品需求文档 (PRD)

版本: v1.0 日期: 2026-03-17 状态: 草案 目标受众: 产品经理、技术架构师、开发团队、投资者


目录

  1. 文档概述
  2. 市场分析与用户痛点
  3. 产品定位与价值主张
  4. 技术架构方案
  5. 核心功能规格
  6. 用户流程设计
  7. 实施路线图
  8. 风险评估与缓解策略
  9. 商业模式
  10. 附录

1. 文档概述

1.1 背景

NAS(网络附加存储)市场长期存在严重的厂商锁定问题。用户一旦选择某个品牌(如群晖Synology、威联通QNAP等),其数据就会被封装在专有的文件系统、RAID格式和元数据结构中。当用户希望更换品牌时,面临的选择往往是:

  • 通过网络拷贝数TB数据(耗时数天)
  • 格式化硬盘丢失所有历史快照和元数据
  • 购买同品牌更昂贵的新设备

1.2 文档目的

本文档定义 MigrateNAS 产品的完整技术方案和商业规划,旨在实现:

"硬盘即插即用,数据零迁移" —— 让用户真正拥有数据的自由迁移权

1.3 术语表

术语解释
SHRSynology Hybrid RAID,群晖专利RAID技术
BtrfsB-tree文件系统,群晖默认文件系统
LVMLogical Volume Manager,逻辑卷管理
MD RAIDLinux Multi-Device RAID,Linux软RAID
Live USB可启动的USB系统,无需安装
FUSEFilesystem in Userspace,用户空间文件系统

2. 市场分析与用户痛点

2.1 目标用户画像

用户A:家庭NAS升级者

  • 场景: 3年前购买群晖DS218+,现需扩容到4盘位
  • 痛点: 新群晖设备贵,想换性价比更高的品牌
  • 数据规模: 8TB,包含家庭照片、视频、文档
  • 技术能力: 中等,能使用命令行但不熟练

用户B:中小企业IT管理员

  • 场景: 公司5台群晖设备,合同到期考虑其他品牌
  • 痛点: 数TB业务数据,无法承受长时间停机
  • 数据规模: 50TB+,含虚拟机、数据库、项目文件
  • 技术能力: 高,熟悉Linux和存储技术

用户C:NAS发烧友/技术爱好者

  • 场景: 自建NAS系统(TrueNAS/Unraid),想导入旧硬盘
  • 痛点: 享受DIY乐趣,但被专有格式限制
  • 数据规模: 20TB+,多为媒体库和实验数据
  • 技术能力: 很高,愿意参与开源贡献

2.2 市场规模

指标数据来源
全球NAS市场规模 (2025)$38.2BGrand View Research
年复合增长率 (CAGR)15.8%2024-2030预测
群晖市场份额~35%IDC报告
潜在迁移需求用户~500万估算(5年+老用户)

2.3 竞品分析

竞品/方案优点缺点我们的优势
群晖Hyper Backup官方支持,可靠仅群晖间迁移跨品牌支持
rsync手动拷贝通用性强需网络传输,慢直接读取硬盘,快
UFS Explorer专业数据恢复贵($100+),只读性价比高,支持写入
RAID Reconstructor支持多种RAID仅Windows,无NAS集成Linux原生,NAS集成

3. 产品定位与价值主张

3.1 产品愿景

成为NAS领域的"数据自由"倡导者,打破厂商锁定,让存储回归用户

3.2 核心价值主张

价值维度描述用户收益
数据主权用户完全掌控自己的数据不再被厂商绑架
零停机迁移硬盘插拔即用,无需网络拷贝节省数天时间
元数据完整保留权限、快照、共享设置无需重新配置
成本节约避免购买同品牌高价设备硬件选择更自由

3.3 产品形态

MigrateNAS ├── 软件产品 │ ├── Live USB系统(个人用户) │ ├── NAS厂商授权SDK(B2B) │ └── 命令行工具包(技术用户) ├── 服务产品 │ ├── 在线迁移服务(远程协助) │ └── 企业级数据迁移咨询 └── 开源项目 ├── 社区版驱动 └── 文档与Wiki

4. 技术架构方案

4.1 整体架构

┌─────────────────────────────────────────────────────────────────┐ │ MigrateNAS 技术架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 用户交互层 (UI Layer) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │ │ │ │ Web管理界面 │ │ 命令行工具 │ │ REST API │ │ │ │ │ │ (React/Vue) │ │ (CLI) │ │ (FastAPI) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────┼───────────────────────────────┐ │ │ │ 业务逻辑层 (Service Layer) │ │ │ │ ┌─────────────┐ ┌────────┴────┐ ┌─────────────────┐ │ │ │ │ │ 迁移引擎 │ │ 元数据转换器 │ │ 阵列重组引擎 │ │ │ │ │ │ Migration │ │ Metadata │ │ RAID Rebuilder │ │ │ │ │ │ Engine │ │ Converter │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────┼───────────────────────────────┐ │ │ │ 兼容驱动层 (Driver Layer) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ │ │ SHR解码器 │ │ Btrfs映射器 │ │ 加密层处理器 │ │ │ │ │ │ SHR Decoder │ │ Btrfs │ │ Encryption Handler │ │ │ │ │ │ │ │ Mapper │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ │ │ 分区解析器 │ │ LVM激活器 │ │ 快照管理器 │ │ │ │ │ │ Partition │ │ LVM │ │ Snapshot Manager │ │ │ │ │ │ Parser │ │ Activator │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────┼───────────────────────────────┐ │ │ │ 内核层 (Kernel Layer) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ │ │ MD RAID驱动 │ │ Btrfs驱动 │ │ LVM2驱动 │ │ │ │ │ │ dm-mod │ │ btrfs │ │ device-mapper │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ │ │ FUSE驱动 │ │ 块设备层 │ │ 文件系统抽象层 │ │ │ │ │ │ fuse │ │ Block Layer │ │ VFS │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────┴───────────────────────────────┐ │ │ │ 硬件层 (Hardware Layer) │ │ │ │ 原群晖硬盘 ─┬─ 硬盘1 (SHR-1成员) │ │ │ │ ├─ 硬盘2 (SHR-1成员) │ │ │ │ ├─ 硬盘3 (Basic单盘) │ │ │ │ └─ 硬盘4 (SSD缓存) │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘

4.2 核心模块详解

4.2.1 SHR逆向兼容引擎

技术原理:

SHR(Synology Hybrid RAID)是群晖的专利技术,本质上是LVM + MD RAID的组合。通过逆向工程,我们发现:

SHR磁盘布局示例(4TB硬盘): ┌─────────────────────────────────────────────────────────────┐ │ MBR/GPT分区表 │ ├─────────────────────────────────────────────────────────────┤ │ 分区1: /dev/sda1 (2GB, Linux RAID) ───┐ │ │ 分区2: /dev/sda2 (3.6TB, Linux RAID) ─┼──┐ │ │ │ │ │ ├──────────────────────────────────────┘ │ │ │ │ │ │ MD设备层: │ │ │ /dev/md0 (RAID1, 系统分区) ◄───────────┘ │ │ /dev/md1 (RAID5, 数据分区) ◄───────────────┐ │ │ │ │ ├─────────────────────────────────────────────┘ │ │ │ │ LVM层: │ │ VG: vg1 (卷组) │ │ ├─ LV: syno_vg1_volume1 (逻辑卷 - 实际数据) │ │ └─ LV: syno_vg1_docker (Docker存储) │ │ │ ├─────────────────────────────────────────────────────────────┤ │ Btrfs文件系统 │ │ ├─ @syno (根子卷) │ │ ├─ @syno@snapshot (快照子卷) │ │ ├─ @docker (Docker子卷) │ │ └─ @iSCSI (iSCSI LUN子卷) │ └─────────────────────────────────────────────────────────────┘

关键代码逻辑:

python
# syno_shr_decoder.py import struct import subprocess from dataclasses import dataclass from typing import List, Optional, Dict from enum import Enum class SHRRaidLevel(Enum): """SHR支持的RAID级别""" BASIC = "basic" # 单盘 RAID0 = "raid0" # 条带 RAID1 = "raid1" # 镜像 RAID5 = "raid5" # 分布式奇偶校验 RAID6 = "raid6" # 双奇偶校验 RAID10 = "raid10" # 镜像+条带 SHR1 = "shr1" # SHR 1盘容错 SHR2 = "shr2" # SHR 2盘容错 @dataclass class DiskInfo: """群晖磁盘信息""" device_path: str serial_number: str model: str size_bytes: int partitions: List['PartitionInfo'] is_synology_disk: bool = False raid_role: Optional[str] = None disk_order: int = -1 @dataclass class PartitionInfo: """分区信息""" number: int start_sector: int end_sector: int size_bytes: int partition_type: str filesystem: Optional[str] = None md_superblock: Optional[Dict] = None class SHRDecoder: """ SHR (Synology Hybrid RAID) 解码器 功能: 1. 识别群晖磁盘特有的分区签名 2. 解析MD RAID超级块 3. 提取LVM配置 4. 重组SHR阵列 """ # 群晖特有的魔术数字和UUID前缀 SYNO_MAGIC = b'Syno' SYNO_UUID_PREFIX = '25534852' # "SHR"的十六进制 MD_SB_OFFSET_V0 = -8 * 1024 * 1024 # 版本0超级块偏移 MD_SB_OFFSET_V1 = -4 * 1024 # 版本1超级块偏移 def __init__(self): self.logger = self._setup_logger() self.detected_arrays: Dict[str, List[DiskInfo]] = {} def scan_all_disks(self) -> List[DiskInfo]: """ 扫描所有连接的磁盘,识别群晖磁盘 Returns: List[DiskInfo]: 检测到的群晖磁盘列表 """ syno_disks = [] # 获取所有块设备 block_devices = self._get_block_devices() for device in block_devices: disk_info = self._analyze_disk(device) if disk_info and disk_info.is_synology_disk: syno_disks.append(disk_info) self.logger.info(f"检测到群晖磁盘: {device} ({disk_info.model})") return syno_disks def _analyze_disk(self, device_path: str) -> Optional[DiskInfo]: """ 分析单个磁盘,检测是否为群晖格式 Args: device_path: 设备路径,如 /dev/sda Returns: DiskInfo: 如果识别为群晖磁盘,返回详细信息 """ # 1. 获取磁盘基本信息 disk_info = self._get_disk_info(device_path) # 2. 扫描分区表 partitions = self._scan_partitions(device_path) disk_info.partitions = partitions # 3. 检测群晖签名 is_syno = self._check_synology_signature(device_path, partitions) disk_info.is_synology_disk = is_syno if is_syno: # 4. 如果是群晖盘,解析RAID信息 self._parse_raid_info(disk_info) return disk_info def _check_synology_signature(self, device: str, partitions: List[PartitionInfo]) -> bool: """ 检测群晖特有的签名 检测点: 1. 分区类型是否为 Linux RAID (0xFD) 2. 分区标签是否包含 "syno" 或 "SYNO" 3. 特定偏移位置是否有群晖魔术数字 """ try: # 检查第一个分区的标签 with open(device, 'rb') as f: # 读取GPT分区表头部 f.seek(512) # 跳过保护MBR gpt_header = f.read(512) # 检查GPT签名 if gpt_header[0:8] != b'EFI PART': # 可能是MBR分区表,检查MBR f.seek(446) mbr_part = f.read(64) # 检查分区类型 if any(mbr_part[i*16+4] == 0xFD for i in range(4)): return self._verify_synology_mbr(device) else: # GPT分区表,检查分区名称 return self._verify_synology_gpt(device, gpt_header) except Exception as e: self.logger.error(f"检查签名失败 {device}: {e}") return False def _parse_raid_info(self, disk_info: DiskInfo): """ 解析RAID超级块信息 """ for part in disk_info.partitions: if part.partition_type == 'fd': # Linux RAID md_info = self._read_md_superblock( disk_info.device_path, part.number ) if md_info: part.md_superblock = md_info disk_info.raid_role = md_info.get('role') disk_info.disk_order = md_info.get('disk_number', -1) def _read_md_superblock(self, device: str, partition_num: int) -> Optional[Dict]: """ 读取MD RAID超级块 MD超级块存储在: - 版本0.90: 分区末尾8MB处 - 版本1.x: 分区开头4KB或末尾4KB """ partition = f"{device}{partition_num}" try: # 使用mdadm查询(如果可用) result = subprocess.run( ['mdadm', '--examine', partition], capture_output=True, text=True, timeout=30 ) if result.returncode == 0: return self._parse_mdadm_output(result.stdout) # 如果mdadm不可用,手动解析 return self._manual_parse_md_sb(partition) except Exception as e: self.logger.warning(f"读取MD超级块失败 {partition}: {e}") return None def assemble_shr_array(self, disks: List[DiskInfo]) -> Dict: """ 组装SHR阵列 流程: 1. 按array_uuid分组磁盘 2. 使用mdadm --assemble组装MD设备 3. 激活LVM卷组 4. 扫描并返回逻辑卷列表 """ if not disks: raise ValueError("没有提供磁盘") # 按阵列UUID分组 arrays = self._group_by_array(disks) assembled = {} for array_uuid, array_disks in arrays.items(): self.logger.info(f"组装阵列: {array_uuid}") # 1. 组装MD RAID md_devices = self._assemble_md_arrays(array_disks) # 2. 激活LVM vg_name = self._detect_volume_group(array_disks) if vg_name: self._activate_volume_group(vg_name) # 3. 获取逻辑卷 logical_volumes = self._scan_logical_volumes(vg_name) assembled[array_uuid] = { 'uuid': array_uuid, 'md_devices': md_devices, 'volume_group': vg_name, 'logical_volumes': logical_volumes, 'disks': [d.device_path for d in array_disks] } return assembled def _activate_volume_group(self, vg_name: str): """ 激活LVM卷组 """ try: # 扫描卷组 subprocess.run( ['vgscan'], check=True, capture_output=True ) # 激活卷组 subprocess.run( ['vgchange', '-ay', vg_name], check=True, capture_output=True ) self.logger.info(f"已激活卷组: {vg_name}") except subprocess.CalledProcessError as e: self.logger.error(f"激活卷组失败 {vg_name}: {e}") raise

4.2.2 Btrfs子卷映射器

python
# btrfs_mapper.py import subprocess import os from typing import List, Dict, Optional from dataclasses import dataclass @dataclass class BtrfsSubvolume: """Btrfs子卷信息""" id: int parent_id: int path: str name: str readonly: bool = False received_uuid: Optional[str] = None class SynoBtrfsMapper: """ 群晖Btrfs子卷映射器 群晖使用特定的子卷命名约定: - @syno: 根数据子卷 - @syno@snapshot: 快照存储 - @docker: Docker镜像和容器数据 - @iSCSI: iSCSI LUN存储 """ SYNO_SUBVOLUME_PREFIXES = { '@syno': 'data', '@syno@snapshot': 'snapshots', '@docker': 'docker', '@iSCSI': 'iscsi', '@swap': 'swap', '@syno@ea': 'extended_attributes' } def __init__(self, mount_point: str): self.mount_point = mount_point self.subvolumes: List[BtrfsSubvolume] = [] self.syno_structure: Dict[str, any] = {} def scan_subvolumes(self) -> List[BtrfsSubvolume]: """ 扫描所有Btrfs子卷 """ try: result = subprocess.run( ['btrfs', 'subvolume', 'list', '-p', '-u', self.mount_point], capture_output=True, text=True, check=True ) self.subvolumes = self._parse_subvolume_list(result.stdout) self._analyze_syno_structure() return self.subvolumes except subprocess.CalledProcessError as e: raise RuntimeError(f"扫描子卷失败: {e.stderr}") def _analyze_syno_structure(self): """ 分析群晖特有的子卷结构 """ structure = { 'root_data': None, 'snapshots': [], 'shares': {}, 'docker': None, 'iscsi': None } for sv in self.subvolumes: path = sv.path # 识别各类子卷 if path == '@syno': structure['root_data'] = sv elif path.startswith('@syno@snapshot'): structure['snapshots'].append(sv) elif path.startswith('@docker'): structure['docker'] = sv elif path.startswith('@iSCSI'): structure['iscsi'] = sv elif path.startswith('@syno/'): # 共享文件夹通常映射为 @syno/photo, @syno/video 等 share_name = path.replace('@syno/', '') structure['shares'][share_name] = sv self.syno_structure = structure def create_user_friendly_mount(self, target_base: str) -> Dict[str, str]: """ 创建用户友好的挂载结构 将群晖的子卷结构转换为用户友好的目录结构: - @syno/photo → /mnt/migrate/Photos - @syno/video → /mnt/migrate/Videos - @syno/homes → /mnt/migrate/Homes """ mount_map = {} # 确保目标目录存在 os.makedirs(target_base, exist_ok=True) # 挂载根数据子卷 if self.syno_structure['root_data']: root_mount = os.path.join(target_base, 'Data') self._mount_subvolume(self.syno_structure['root_data'], root_mount) mount_map['root'] = root_mount # 为每个共享文件夹创建独立挂载点 for share_name, subvol in self.syno_structure['shares'].items(): # 转换为友好的显示名称 friendly_name = self._convert_share_name(share_name) share_mount = os.path.join(target_base, friendly_name) self._mount_subvolume(subvol, share_mount) mount_map[share_name] = share_mount return mount_map def _convert_share_name(self, syno_name: str) -> str: """ 转换群晖共享文件夹名称为用户友好名称 """ name_map = { 'photo': 'Photos', 'video': 'Videos', 'music': 'Music', 'home': 'Home', 'homes': 'Homes', 'docker': 'Docker', 'web': 'Web', 'surveillance': 'Surveillance' } return name_map.get(syno_name.lower(), syno_name.title()) def migrate_snapshots(self, target_path: str) -> List[str]: """ 迁移Btrfs快照到新系统 策略: 1. 只读发送快照到目标位置 2. 在新系统创建对应的快照结构 """ migrated = [] for snapshot in self.syno_structure['snapshots']: try: # 使用btrfs send/receive迁移快照 snapshot_name = os.path.basename(snapshot.path) target_snapshot = os.path.join(target_path, snapshot_name) self._send_receive_snapshot(snapshot, target_snapshot) migrated.append(target_snapshot) except Exception as e: self.logger.warning(f"迁移快照失败 {snapshot.path}: {e}") return migrated def _mount_subvolume(self, subvolume: BtrfsSubvolume, mount_point: str): """ 挂载指定的子卷 """ os.makedirs(mount_point, exist_ok=True) # 构建挂载选项 mount_opts = f"subvolid={subvolume.id},noatime" subprocess.run( ['mount', '-o', mount_opts, self.mount_point, mount_point], check=True )

4.2.3 元数据迁移引擎

python
# metadata_migrator.py import json import sqlite3 import xml.etree.ElementTree as ET from typing import Dict, List, Optional from dataclasses import dataclass, asdict from pathlib import Path @dataclass class UserAccount: """用户账户信息""" uid: int username: str description: str email: Optional[str] password_hash: str # 可能需要重新加密 groups: List[str] home_directory: str quota_bytes: Optional[int] = None @dataclass class SharedFolder: """共享文件夹配置""" name: str path: str description: str enable_recycle_bin: bool enable_encryption: bool hide_unreadable: bool acl_entries: List[Dict] # 访问控制列表 @dataclass class SynoMetadata: """完整的群晖元数据结构""" system_info: Dict users: List[UserAccount] shared_folders: List[SharedFolder] network_config: Dict application_settings: Dict snapshots: List[Dict] class SynoMetadataExtractor: """ 群晖元数据提取器 群晖将配置存储在: - /etc/synoinfo.conf (系统配置) - /etc/passwd, /etc/shadow (用户信息) - /usr/syno/etc/acl/ (ACL配置) - /usr/syno/etc/packages/ (应用配置) - /var/lib/synoindex/ (索引数据库) """ def __init__(self, config_partition_path: str): self.config_path = Path(config_partition_path) self.metadata = SynoMetadata( system_info={}, users=[], shared_folders=[], network_config={}, application_settings={}, snapshots=[] ) def extract_all(self) -> SynoMetadata: """ 提取所有元数据 """ self.metadata.system_info = self._extract_system_info() self.metadata.users = self._extract_users() self.metadata.shared_folders = self._extract_shared_folders() self.metadata.network_config = self._extract_network_config() self.metadata.application_settings = self._extract_app_settings() self.metadata.snapshots = self._extract_snapshot_info() return self.metadata def _extract_system_info(self) -> Dict: """ 提取系统基本信息 """ synoinfo_path = self.config_path / 'etc' / 'synoinfo.conf' info = {} if synoinfo_path.exists(): with open(synoinfo_path, 'r') as f: for line in f: line = line.strip() if line and not line.startswith('#'): # 解析 key="value" 格式 if '=' in line: key, value = line.split('=', 1) info[key] = value.strip('"\'') return info def _extract_users(self) -> List[UserAccount]: """ 提取用户账户信息 """ users = [] passwd_path = self.config_path / 'etc' / 'passwd' shadow_path = self.config_path / 'etc' / 'shadow' if not passwd_path.exists(): return users # 读取passwd with open(passwd_path, 'r') as f: for line in f: parts = line.strip().split(':') if len(parts) >= 6: username = parts[0] uid = int(parts[2]) gid = int(parts[3]) home = parts[5] shell = parts[6] if len(parts) > 6 else '' # 跳过系统账户 if uid < 1000 and username not in ['admin', 'guest']: continue user = UserAccount( uid=uid, username=username, description='', email=None, password_hash='*', groups=[], home_directory=home ) users.append(user) # 读取shadow获取密码哈希 if shadow_path.exists(): shadow_data = {} with open(shadow_path, 'r') as f: for line in f: parts = line.strip().split(':') if len(parts) >= 2: shadow_data[parts[0]] = parts[1] for user in users: if user.username in shadow_data: user.password_hash = shadow_data[user.username] # 读取额外的用户信息(描述、邮箱等) self._enrich_user_info(users) return users def _extract_shared_folders(self) -> List[SharedFolder]: """ 提取共享文件夹配置 """ shares = [] # 群晖共享文件夹配置在多个位置 share_conf_paths = [ self.config_path / 'usr' / 'syno' / 'etc' / 'share_right.map', self.config_path / 'usr' / 'syno' / 'etc' / 'shared' / 'settings' ] for conf_path in share_conf_paths: if conf_path.exists(): shares.extend(self._parse_share_config(conf_path)) # 提取ACL信息 self._enrich_acl_info(shares) return shares def _enrich_acl_info(self, shares: List[SharedFolder]): """ 为共享文件夹添加ACL信息 """ acl_path = self.config_path / 'usr' / 'syno' / 'etc' / 'acl' for share in shares: acl_file = acl_path / f"{share.name}.acl" if acl_file.exists(): share.acl_entries = self._parse_acl_file(acl_file) class MetadataConverter: """ 元数据转换器基类 """ def convert(self, syno_metadata: SynoMetadata) -> Dict: """ 将群晖元数据转换为目标格式 """ raise NotImplementedError class QNAPConverter(MetadataConverter): """ 转换为威联通QNAP格式 """ def convert(self, syno_metadata: SynoMetadata) -> Dict: qnap_config = { 'system': self._convert_system_info(syno_metadata.system_info), 'users': self._convert_users(syno_metadata.users), 'shares': self._convert_shares(syno_metadata.shared_folders), 'network': self._convert_network(syno_metadata.network_config) } return qnap_config def _convert_users(self, users: List[UserAccount]) -> List[Dict]: """ 转换用户格式 QNAP使用不同的密码加密方式,需要重新设置密码 """ converted = [] for user in users: converted.append({ 'username': user.username, 'description': user.description, 'email': user.email or '', 'password': '__RESET_REQUIRED__', # 标记需要重置 'uid': user.uid, 'home': f"/share/homes/{user.username}", 'groups': user.groups }) return converted class TrueNASConverter(MetadataConverter): """ 转换为TrueNAS格式 """ def convert(self, syno_metadata: SynoMetadata) -> Dict: # TrueNAS使用数据库存储配置 return { 'database_inserts': self._generate_sql_inserts(syno_metadata), 'middleware_calls': self._generate_middleware_calls(syno_metadata) }

4.3 部署方案

方案A:Live USB启动盘(个人用户)

yaml
# live_usb_config.yaml system: name: MigrateNAS Live version: "1.0.0" base: Debian 12 (Bookworm) kernel: 6.1.x with custom modules boot_modes: - name: GUI Mode description: 图形界面向导模式 default: true - name: CLI Mode description: 命令行专家模式 - name: Rescue Mode description: 数据救援模式(只读) included_packages: # RAID支持 - mdadm - dmraid # LVM支持 - lvm2 - thin-provisioning-tools # 文件系统 - btrfs-progs - xfsprogs - e2fsprogs - zfsutils-linux # 网络工具 - rsync - openssh-server - nfs-common - cifs-utils # 诊断工具 - smartmontools - hdparam - nvme-cli - sdparm # UI依赖 - xorg - openbox - chromium - python3-tk custom_modules: - syno_shr_decoder.ko - syno_btrfs_helper.ko - disk_identify.ko auto_start_services: - migrate_nas_daemon - web_ui_server - disk_monitor partition_layout: boot: size: 512MB type: FAT32 flags: [boot, esp] root: size: 4GB type: ext4 compressed: true persistence: size: 2GB type: ext4 label: MIGRATE_DATA # 用于保存配置和日志

方案B:NAS厂商SDK集成(B2B)

MigrateNAS SDK 架构 ├── 内核模块层 │ ├── syno_compat.ko # 核心兼容驱动 │ ├── shr_decoder.ko # SHR解码 │ └── btrfs_ext.ko # Btrfs扩展 │ ├── 用户空间库 │ ├── libmigrate.so # 主库 │ ├── libshr.so # SHR处理 │ └── libsyno_meta.so # 元数据处理 │ ├── 头文件 │ ├── migrate_nas.h # 主API头文件 │ ├── shr_types.h # SHR类型定义 │ └── meta_types.h # 元数据类型 │ ├── 示例代码 │ ├── example_basic.c # 基础使用示例 │ ├── example_advanced.c # 高级功能示例 │ └── example_qnap.cpp # QNAP集成示例 │ └── 文档 ├── API_REFERENCE.md ├── INTEGRATION_GUIDE.md └── TROUBLESHOOTING.md

5. 核心功能规格

5.1 功能矩阵

功能模块功能点社区版专业版企业版优先级
磁盘识别自动检测群晖硬盘P0
显示磁盘健康状态P0
支持SATA/SAS/NVMeP0
RAID支持Basic单盘只读P0
Basic单盘读写-P0
标准RAID 0/1/5/6/10P0
SHR-1阵列只读-P1
SHR-1阵列读写-P1
SHR-2阵列支持-P2
SSD缓存识别-P2
文件系统Btrfs标准功能P0
Btrfs快照读取-P1
Btrfs快照迁移-P2
ext4支持P0
加密磁盘解密-P2
元数据共享文件夹结构-P1
用户账户迁移-P1
权限ACL转换-P2
快照历史保留--P3
应用数据Docker容器迁移--P3
虚拟机磁盘转换--P3
iSCSI LUN支持--P3
目标平台TrueNAS COREP0
TrueNAS SCALEP0
QNAP QTS-P1
ASUSTOR ADM-P2
UnraidP1
通用LinuxP0

5.2 性能规格

指标目标值测试方法
磁盘识别时间< 5秒/盘插入后自动检测
RAID组装时间< 30秒SHR-1 4盘位测试
顺序读取速度> 90% 原生与群晖本机对比
顺序写入速度> 85% 原生与群晖本机对比
随机IOPS> 80% 原生fio测试
内存占用< 512MBLive USB运行时
系统启动时间< 60秒从USB启动到可操作

5.3 兼容性规格

支持的群晖系统版本:

  • DSM 6.0 - 7.2+
  • 支持x86_64和ARM架构

支持的存储池类型:

  • Basic
  • JBOD
  • RAID 0, 1, 5, 6, 10
  • SHR, SHR-2

支持的文件系统:

  • Btrfs (含压缩、去重)
  • ext4

限制条件:

  • 硬件RAID卡:不支持(需IT模式)
  • eCryptfs加密:需密码
  • 全盘加密:需密钥文件

6. 用户流程设计

6.1 典型使用场景

场景A:个人用户Basic盘迁移

用户: 小张,想从群晖DS218+换到TrueNAS 数据: 1块8TB硬盘,Basic单盘,存储家庭照片 操作流程: ┌─────────────────────────────────────────────────────────────┐ │ 1. 准备工作 │ │ □ 下载 MigrateNAS Live USB 镜像 │ │ □ 使用 Rufus/balenaEtcher 制作启动盘 │ │ □ 关闭群晖NAS电源,拔出硬盘 │ │ □ 将硬盘插入新NAS设备(任意盘位) │ │ □ 插入USB启动盘,开机 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 2. 自动检测 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ MigrateNAS 启动中... │ │ │ │ │ │ │ │ [==========] 正在扫描磁盘... │ │ │ │ │ │ │ │ ✓ 检测到 1 块群晖硬盘 │ │ │ │ - /dev/sda: 8.0 TB, Basic, 状态: 健康 │ │ │ │ │ │ │ │ [继续] [查看详情] [取消] │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 3. 选择迁移模式 │ │ ○ 模式A: 只读挂载(安全预览,推荐初次使用) │ │ ◉ 模式B: 完全导入(将数据融入新系统) │ │ ○ 模式C: 数据导出(复制到其他存储) │ │ │ │ [选中模式B] │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 模式B 详情: │ │ │ │ - 保留原有数据不动 │ │ │ │ - 在新系统创建指向原盘的链接 │ │ │ │ - 可以读写,但拔下后群晖仍可识别 │ │ │ │ - 部分高级功能可能受限 │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 4. 配置目标系统 │ │ 检测到目标系统: TrueNAS SCALE │ │ │ │ □ 保留共享文件夹名称 (Photo → Photo) │ │ □ 创建对应的数据集结构 │ │ □ 迁移用户权限配置(如适用) │ │ │ │ 预计耗时: < 5分钟 │ │ [开始迁移] │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 5. 完成 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ ✓ 迁移完成! │ │ │ │ │ │ │ │ 您的数据现在可以在以下位置访问: │ │ │ │ - SMB: \\truenas\Photo │ │ │ │ - NFS: /mnt/tank/Photo │ │ │ │ - WebUI: http://truenas.local │ │ │ │ │ │ │ │ [重启到TrueNAS] [关机] [返回主菜单] │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

场景B:企业SHR阵列迁移

用户: IT管理员,公司群晖RS1221+,需迁移到TrueNAS 数据: 4块8TB硬盘,SHR-1阵列,含虚拟机、数据库、共享文件 特殊要求: 零停机或最小停机时间 操作流程: 1. 使用 MigrateNAS 评估模式(只读)验证阵列可识别 2. 准备新NAS设备,安装目标系统(TrueNAS) 3. 使用MigrateNAS Live USB启动,选择"热迁移"模式 4. 配置增量同步策略 5. 在维护窗口执行最终切换

6.2 命令行界面(CLI)

bash
# MigrateNAS CLI 命令参考 # 扫描磁盘 migrate-nas scan # 输出: # [INFO] Scanning all block devices... # [FOUND] /dev/sda: Synology Disk, 8TB, Model: ST8000VN004 # [FOUND] /dev/sdb: Synology Disk, 8TB, Model: ST8000VN004 # [INFO] Detected SHR-1 array with 2 disks # 检查阵列状态 migrate-nas check --disks /dev/sda,/dev/sdb # 输出详细的阵列信息和健康状态 # 组装阵列(只读) migrate-nas assemble --disks /dev/sda,/dev/sdb --readonly # 组装阵列(读写) migrate-nas assemble --disks /dev/sda,/dev/sdb --read-write # 导出元数据 migrate-nas export-meta --source /dev/md1 --output ./syno_meta.json # 转换配置 migrate-nas convert --input ./syno_meta.json --target truenas --output ./truenas_config.json # 迁移数据(rsync包装) migrate-nas sync --source /mnt/syno --destination /mnt/truenas --progress # 完整迁移(自动化流程) migrate-nas migrate \ --disks /dev/sda,/dev/sdb \ --target-system truenas \ --mode hot-migrate \ --config ./migration.conf

7. 实施路线图

7.1 开发阶段

gantt
    title MigrateNAS 开发路线图
    dateFormat  YYYY-MM
    section Phase 1: 基础
    技术调研与原型        :done, p1_1, 2024-01, 2024-02
    Btrfs Basic支持       :active, p1_2, after p1_1, 2024-03
    Live USB基础镜像      :p1_3, after p1_2, 2024-04
    CLI工具开发           :p1_4, after p1_2, 2024-04

    section Phase 2: 核心
    SHR-1解码器           :p2_1, after p1_4, 2024-05
    标准RAID支持          :p2_2, after p1_4, 2024-05
    GUI向导界面           :p2_3, after p2_1, 2024-06
    元数据提取器          :p2_4, after p2_2, 2024-06

    section Phase 3: 扩展
    SHR-2支持            :p3_1, after p2_3, 2024-07
    快照迁移             :p3_2, after p2_4, 2024-07
    QNAP适配器           :p3_3, after p3_1, 2024-08
    ASUSTOR适配器        :p3_4, after p3_3, 2024-09

    section Phase 4: 商业化
    SDK开发             :p4_1, after p3_4, 2024-10
    企业支持服务         :p4_2, after p4_1, 2024-11
    市场推广            :p4_3, after p4_2, 2024-12

7.2 里程碑

阶段时间里程碑交付物
MVPQ1 2026基础可读CLI工具,Basic盘只读支持
v1.0Q2 2026核心功能GUI界面,SHR-1读写支持
v1.5Q3 2026多平台QNAP/ASUSTOR适配
v2.0Q4 2026商业化SDK,企业服务,市场推广

7.3 资源需求

角色人数主要职责
技术负责人1架构设计,技术决策
Linux内核工程师2驱动开发,文件系统
后端工程师2服务开发,API设计
前端工程师1GUI界面开发
QA工程师1测试,兼容性验证
产品经理1需求分析,项目管理
技术支持1文档,社区支持

8. 风险评估与缓解策略

8.1 技术风险

风险可能性影响缓解策略
SHR专利纠纷基于开源MD+LVM实现,避免专利侵权;法律咨询
数据损坏极高默认只读模式;完整测试;数据校验
兼容性问题建立测试矩阵;社区反馈机制;快速迭代
群晖系统更新导致不兼容持续监控DSM更新;自动化测试

8.2 商业风险

风险可能性影响缓解策略
群晖官方对抗保持技术中立;开源社区支持;法律合规
市场需求不足前期市场调研;MVP验证;灵活转型
竞争对手复制建立技术壁垒;快速迭代;社区生态

8.3 安全考虑

安全设计原则: ┌─────────────────────────────────────────────────────────────┐ │ 1. 默认只读 │ │ - 所有操作默认以只读模式执行 │ │ - 明确用户确认后才允许写操作 │ │ │ │ 2. 数据完整性校验 │ │ - Btrfs校验和验证 │ │ - 定期数据一致性检查 │ │ │ │ 3. 操作审计日志 │ │ - 记录所有修改操作 │ │ - 支持操作回滚 │ │ │ │ 4. 加密安全 │ │ - 密码/密钥不上传云端 │ │ - 内存中不保留敏感信息 │ │ │ │ 5. 沙箱测试 │ │ - 提供虚拟化测试环境 │ │ - 建议用户先测试再正式迁移 │ └─────────────────────────────────────────────────────────────┘

9. 商业模式

9.1 收入模式

收入流 ├── 软件销售 │ ├── 专业版许可证 ($49/设备) │ ├── 企业版订阅 ($99/年/节点) │ └── 批量许可证折扣 │ ├── B2B授权 │ ├── NAS厂商SDK授权费 │ ├── 按设备抽成 ($5-10/台) │ └── 技术支持年费 │ ├── 服务收入 │ ├── 远程迁移服务 ($299/次起) │ ├── 企业级数据恢复 │ └── 培训与咨询 │ └── 开源生态 ├── 社区版(免费,品牌曝光) ├── 企业支持合同 └── 定制开发服务

9.2 定价策略

版本价格目标用户包含内容
社区版免费技术爱好者基础只读功能,社区支持
专业版$49/设备个人/小型工作室完整功能,邮件支持
企业版$999/年企业IT部门无限设备,优先支持,SLA
厂商SDK面议NAS制造商源码授权,定制开发

9.3 市场推广策略

  1. 技术社区渗透

    • Reddit r/homelab, r/DataHoarder
    • TrueNAS/QNAP官方论坛
    • 知乎、V2EX中文社区
  2. 内容营销

    • 迁移案例研究
    • 技术博客文章
    • YouTube/Bilibili教程视频
  3. 合作伙伴

    • NAS硬件零售商(捆绑销售)
    • 数据恢复服务商
    • 开源存储项目合作

10. 附录

10.1 技术参考资料

群晖技术文档:

Linux存储技术:

相关开源项目:

10.2 测试设备清单

群晖设备DSM版本测试配置
DS218+7.22x4TB SHR-1
DS920+7.24x8TB SHR-1
DS1621+7.26x4TB RAID5
RS1221+7.18x8TB SHR-2
DS1187.21x4TB Basic

10.3 常见问题(FAQ)

Q: 使用MigrateNAS会 void 群晖保修吗? A: 不会。MigrateNAS只读取磁盘数据,不对群晖硬件做任何修改。

Q: 迁移后还能插回群晖使用吗? A: 只读模式可以;读写模式(模式B)理论上也可以,但建议先备份。

Q: 支持加密的群晖磁盘吗? A: 支持,但需要您提供正确的密码或密钥文件。

Q: 数据会丢失吗? A: 社区版/只读模式不会修改任何数据。写操作前会明确提示并建议备份。

Q: 支持M.2 SSD缓存盘吗? A: 可以识别,但缓存数据通常不需要迁移。


文档修订历史

版本日期作者变更说明
0.12026-03-17Claude初始版本,创建完整PRD

本文档为产品规划文档,内容可能随产品迭代而更新。

本文作者:oyph

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!