diff --git a/.trae/skills/ocr-extract/parse_sku_prices.py b/.trae/skills/ocr-extract/parse_sku_prices.py new file mode 100644 index 0000000..18d40c7 --- /dev/null +++ b/.trae/skills/ocr-extract/parse_sku_prices.py @@ -0,0 +1,366 @@ +import json +import re +import os + +# Load OCR results +with open(r"D:\workspace\membank\售价\ocr_results.json", "r", encoding="utf-8") as f: + all_results = json.load(f) + +# Extract structured SKU prices from each image +# Pattern: SKU name patterns like "蜗牛Nas-xxx-4g+64g", "J1900-4g+32g", etc. +# Prices appear as decimal numbers typically followed by a discount of 5.00 + +def extract_sku_prices(text_lines): + """Extract SKU name and price pairs from OCR text lines""" + entries = [] + i = 0 + while i < len(text_lines): + text, conf = text_lines[i] + text = text.strip() + + # Look for SKU names containing known patterns + sku_patterns = [ + r'(?:蜗牛Nas|蜗牛|Nas|cNas)[\s\w\-\.\+\(\)一-鿿]+?\d+g[+一-鿿]*\d*g', + r'(?:J1900|j1900|J3160|j3160|I3|i3|I5|i5|1037U|1037u|N5105|N5100|N5095|J3355|J3060)[\-\.\+\(\)\s\w一-鿿]*?\d+g[\+一-鿿]*\d*g', + r'(?:J1900|j1900|J3160|j3160|I3|i3|I5|i5|1037U|1037u|N5105|N5100|N5095|J3355|J3060)[\-\.\+\(\)\s\w一-鿿]*?\d+g', + r'(?:16G[BW])[\-\s]*(?:windows|蜗牛|群晖)', + r'(?:DDR|D3|D4)[\-\.\s]*\d+\s*[Gg]', + ] + + is_sku = False + for pat in sku_patterns: + if re.search(pat, text, re.IGNORECASE): + is_sku = True + break + + if is_sku: + sku_name = text + # Look for price pattern nearby: two numbers where one is ~5.00 off from the other + # Pattern: price1 (like 247.50) ... 5.00 ... price2 (like 242.50) + prices_found = [] + for j in range(i, min(i + 10, len(text_lines))): + t2 = text_lines[j][0].strip() + # Match decimal prices + price_match = re.findall(r'\d{2,4}\.\d{2}', t2) + for p in price_match: + prices_found.append(float(p)) + # Also match integer prices + price_match2 = re.findall(r'(? 10 and (p - 5) in prices_found: + actual_price = p # The larger one is the original price + break + + if actual_price: + discounted = actual_price - 5 + entries.append({ + 'sku': sku_name, + 'price': discounted, + 'original_price': actual_price, + 'confidence': conf + }) + + i += 1 + + return entries + +def extract_prices_v2(text_lines): + """ + Better approach: parse each image by looking for patterns: + SKU name -> (price, discount 5.00, discounted_price) + """ + results = [] + + # Flatten texts + all_text = [t[0].strip() for t in text_lines] + all_with_conf = list(text_lines) + + # Known CPU/memory patterns + pattern = re.compile( + r'.*?(?:蜗牛|Nas|NAS|nas|cNas|黑裙|黑群晖|客服|咨询).*?(?:' + r'J1900|j1900|J3160|j3160|' + r'i3|I3|i5|I5|' + r'1037[Uu]|1037u|' + r'N5105|N5100|N5095|' + r'J3355|J3060|' + r'16G[Bb]' + r').*', + re.IGNORECASE + ) + + # Scan through all texts looking for price patterns + for i, (text, conf) in enumerate(all_with_conf): + text = text.strip() + + # Skip non-SKU lines + if len(text) < 3: + continue + + # Check if this looks like a SKU description + has_cpu = bool(re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|N5105|N5100|N5095|J3355|J3060|J4125)', text)) + has_nas = bool(re.search(r'(蜗牛|Nas|NAS|cNas|黑裙)', text)) + has_mem = bool(re.search(r'\d+g[\+一-鿿]*\d+g|\d+g\b|16G[Bb]', text)) + + if has_cpu or (has_nas and has_mem) or ('16G' in text.upper() and ('windows' in text.lower() or '蜗牛' in text or '群晖' in text)): + # This is a SKU name - look for nearby prices + sku_name = text + + # Look at next 8 lines for prices + prices = [] + for j in range(i+1, min(i+12, len(all_with_conf))): + t = all_with_conf[j][0].strip() + # Find decimal numbers + nums = re.findall(r'(\d{2,4}\.\d{2})', t) + prices.extend([float(n) for n in nums]) + + # Also look at previous 2 lines + for j in range(max(0, i-2), i): + t = all_with_conf[j][0].strip() + nums = re.findall(r'(\d{2,4}\.\d{2})', t) + prices.extend([float(n) for n in nums]) + + if prices: + # Find pairs where diff = 5.00 + prices = sorted(set(prices)) + found_price = None + for p in prices: + if p > 20 and (p - 5.00) in [round(x, 2) for x in prices]: + found_price = p + break + + # If no pair found, take the most common price range + if not found_price: + valid_prices = [p for p in prices if 100 < p < 500] + if valid_prices: + found_price = max(valid_prices) + + if found_price and found_price > 20: + discounted = round(found_price - 5.0, 2) + results.append({ + 'sku': sku_name, + 'price': discounted, + 'original_price': round(found_price, 2), + 'confidence': float(conf) if conf else 0 + }) + + # Deduplicate - keep highest confidence for each SKU + seen = {} + for r in results: + key = r['sku'] + if key not in seen or r['confidence'] > seen[key]['confidence']: + seen[key] = r + + return sorted(seen.values(), key=lambda x: x['price']) + + +def extract_prices_v3(text_lines): + """ + Most robust: look for every ... ... 5.00 ... pattern + where price_a = price_b - 5 (or vice versa) + """ + results = [] + all_with_conf = list(text_lines) + + for i, (text, conf) in enumerate(all_with_conf): + text = text.strip() + + # Must contain key SKU indicators + has_indicator = ( + bool(re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|N5105|N5100|N5095|J3355|J3060|J4125|5005)', text)) or + bool(re.search(r'(蜗牛|Nas|NAS|黑裙|黑群晖|客服|咨询)', text)) or + bool(re.search(r'16G[Bb].*(?:windows|蜗牛|群晖|系统)', text)) or + bool(re.search(r'(?:预装系统|系统).*(?:双盘位|单盘位)', text)) + ) + + if not has_indicator or len(text) < 4: + continue + + # But also filter out pure UI text + skip_patterns = ['商品价格', '关注券', '折扣', '券后价', '商品ID', '活动', '修改', '查看', '营销', '官方', '拼多多', '商家'] + if any(s in text for s in skip_patterns): + continue + + sku_name = text + + # Search window around this line for prices + window_texts = [] + for j in range(max(0, i-3), min(i+15, len(all_with_conf))): + window_texts.append(all_with_conf[j][0].strip()) + + # Extract all decimal prices from the window + prices = [] + for wt in window_texts: + nums = re.findall(r'(\d{2,4}\.\d{2})', wt) + prices.extend([float(n) for n in nums]) + + if not prices: + continue + + prices = sorted(set(prices)) + + # Filter noise prices (too small) + prices = [p for p in prices if p > 15] + + # Find the discount pattern: original_price and (original_price - 5) + found_price = None + price_set = set(round(p, 2) for p in prices) + + for p in prices: + if round(p - 5.0, 2) in price_set: + # p is the higher (original), p-5 is the discounted + found_price = p + break + if round(p + 5.0, 2) in price_set: + # p is the discounted, p+5 is the original + found_price = p + 5.0 + break + + if not found_price: + # Fallback: take the highest price that looks like a product price + valid = [p for p in prices if 100 < p < 600] + if valid: + found_price = max(valid) + + if found_price and found_price > 20: + results.append({ + 'sku': sku_name, + 'price': round(found_price - 5.0, 2), + 'original_price': round(found_price, 2), + 'confidence': float(conf) if conf else 0 + }) + + # Deduplicate by SKU name similarity + def sku_key(s): + return re.sub(r'[\s\-\.\+]+', '', s.lower()) + + deduped = {} + for r in results: + key = sku_key(r['sku']) + if key not in deduped or r['confidence'] > deduped[key]['confidence']: + deduped[key] = r + + return sorted(deduped.values(), key=lambda x: x['price']) + + +print("=" * 80) +print("Extracting SKU Price Summary") +print("=" * 80) + +all_entries = [] +for img_name, lines in all_results.items(): + print(f"\nProcessing: {img_name}") + entries = extract_prices_v3(lines) + print(f" Found {len(entries)} SKUs") + all_entries.extend(entries) + +# Global deduplication +def normalize_sku(name): + """Normalize SKU name for deduplication""" + name = name.strip() + # Remove leading symbols + name = re.sub(r'^[>\s\-\.]+', '', name) + # Standardize separators + name = name.replace('+', '+').replace('+', '+') + # Remove trailing garbage + name = re.sub(r'[>\s\.\-]+$', '', name) + return name + +# Categorize entries +# Dedup keeping highest confidence +global_dedup = {} +for e in all_entries: + norm = normalize_sku(e['sku']) + # Further normalize for key + key = re.sub(r'[\s\-\>\.\(\)]', '', norm.lower()) + # Extract CPU type for grouping + if key not in global_dedup or e['confidence'] > global_dedup[key]['confidence']: + global_dedup[key] = e + +final_entries = sorted(global_dedup.values(), key=lambda x: x['price']) + +# Group by CPU/platform +groups = {} +for e in final_entries: + name = e['sku'] + # Extract group + if '16G' in name.upper() or '16g' in name.lower(): + group = '系统U盘 (16GB)' + elif re.search(r'(i3|I3|i5|I5)', name): + if '双盘' in name or '雙盤' in name: + group = 'NAS 双盘位 - i3/i5系列' + else: + group = 'NAS - i3/i5系列' + elif re.search(r'1037[Uu]', name): + if '单盘' in name or '單盤' in name or 'A4' in name: + group = 'NAS 单盘位 - 1037U系列' + elif '双盘' in name or '雙盤' in name: + group = 'NAS 双盘位 - 1037U系列' + else: + group = 'NAS - 1037U系列' + elif re.search(r'J1900|j1900', name): + group = 'NAS 双盘位 - J1900系列' + elif re.search(r'i3|I3', name): + group = 'NAS - i3系列' + elif re.search(r'i5|I5', name): + group = 'NAS - i5系列' + elif '黑裙' in name or '客服' in name or '咨询' in name: + group = 'NAS - 黑裙晖系列' + else: + group = '其他' + + if group not in groups: + groups[group] = [] + groups[group].append(e) + +# Generate markdown output +md_lines = [] +md_lines.append("# SKU 售价汇总") +md_lines.append("") +md_lines.append(f"> 从拼多多商家后台截图 OCR 识别提取") +md_lines.append(f"> 提取日期: 2026-06-16") +md_lines.append(f"> 数据来源: `售价/` 目录下 {len(all_results)} 张截图") +md_lines.append(f"> 共识别 {len(final_entries)} 个 SKU 售价(已去重)") +md_lines.append("") +md_lines.append("---") +md_lines.append("") + +for group_name in sorted(groups.keys()): + entries = groups[group_name] + md_lines.append(f"## {group_name}") + md_lines.append("") + md_lines.append("| SKU 名称 | 券后售价 (¥) | 券前原价 (¥) | OCR 置信度 |") + md_lines.append("|---------|------------:|------------:|----------:|") + + for e in entries: + name = normalize_sku(e['sku']) + # Clean up OCR artifacts in name + name = name.replace('關注券5元优惠券', '').strip() + name = re.sub(r'\s+', ' ', name) + md_lines.append(f"| {name} | {e['price']:.2f} | {e['original_price']:.2f} | {e['confidence']:.2f} |") + + md_lines.append("") + +md_lines.append("---") +md_lines.append("") +md_lines.append("## 说明") +md_lines.append("") +md_lines.append("- **券后售价** = 券前原价 - 5.00元 (店铺关注券)") +md_lines.append("- 价格数据通过 RapidOCR 从拼多多商家后台截图自动识别提取") +md_lines.append("- 同一SKU在不同截图中出现时保留置信度最高的识别结果") +md_lines.append("- OCR 识别可能存在误差,请以实际后台数据为准") + +output_path = r"D:\workspace\membank\售价\SKU售价汇总.md" +with open(output_path, "w", encoding="utf-8") as f: + f.write("\n".join(md_lines)) + +print(f"\n\nSummary saved to: {output_path}") +print(f"Total: {len(final_entries)} SKUs, {len(groups)} categories") diff --git a/.trae/skills/ocr-extract/parse_v2.py b/.trae/skills/ocr-extract/parse_v2.py new file mode 100644 index 0000000..96c5fa8 --- /dev/null +++ b/.trae/skills/ocr-extract/parse_v2.py @@ -0,0 +1,258 @@ +import json +import re +import os + +# Load OCR results +with open(r"D:\workspace\membank\售价\ocr_results.json", "r", encoding="utf-8") as f: + all_results = json.load(f) + +def extract_sku_v4(text_lines): + """ + Robust extraction: + Pattern in PDD backend: SKU_name ... [price1] ... [5.00] ... [price2] + where price2 = price1 - 5 + """ + results = [] + + # Build full text with confidences + items = [(t[0].strip(), float(t[1]) if t[1] else 0.0) for t in text_lines] + + i = 0 + while i < len(items): + text, conf = items[i] + + # Check for price patterns - find lines with two prices and 5.00 in between + # Look ahead for price patterns + prices_in_window = [] + window_items = [] + + for j in range(i, min(i + 8, len(items))): + t = items[j][0] + nums = re.findall(r'(\d{2,4}\.\d{2})', t) + if nums: + prices_in_window.extend([float(n) for n in nums]) + window_items.append((j, t, [float(n) for n in nums])) + + if not prices_in_window: + i += 1 + continue + + prices_set = set(round(p, 2) for p in prices_in_window) + + # Try to find original price and discounted price (diff = 5) + found_prices = [] + for p in sorted(prices_set): + if round(p - 5, 2) in prices_set and p > 15: + found_prices.append((round(p - 5, 2), p)) # (discounted, original) + elif round(p + 5, 2) in prices_set and p > 15: + found_prices.append((p, round(p + 5, 2))) + + if not found_prices: + i += 1 + continue + + # Now find the SKU name - look backwards from the price + # The SKU name is typically 1-3 lines before the price + sku_name = None + for j in range(i-1, max(0, i-5), -1): + t = items[j][0] + # Must contain CPU or platform identifiers + if re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|5005[Uu]|N5105|16G[Bb]|预装系统|双盘位|单盘位|三盘位|四盘位|黑裙|黑群晖|飞牛|蜗牛|Nas|NAS|盘位)', t): + # Not a pure UI element + if not re.match(r'^(商品|营销|官方|拼多多|商家|查看|修改|活动|价格|关注|折扣|券后|ID)', t): + sku_name = t + break + + if sku_name: + for discounted, original in found_prices: + results.append({ + 'sku': sku_name, + 'price': discounted, + 'original_price': original, + 'confidence': conf + }) + + i += 1 + + return results + + +def normalize_sku_key(name): + """Create a normalized key for deduplication by extracting core identifiers""" + name = name.strip() + # Extract CPU type + cpu = '' + cpu_match = re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|5005[Uu]|N5105|N5095|J3355)', name) + if cpu_match: + cpu = cpu_match.group(1).upper().replace('J1900','J1900').replace('J3160','J3160') + + # Extract memory + mem = '' + mem_match = re.search(r'(\d+[Gg])[+]?(\d+[Gg])?', name) + if mem_match: + mem = mem_match.group(0).upper() + + # Extract form factor + form = '' + if '单盘' in name or '单盤' in name: + form = '单盘' + elif '双盘' in name or '雙盤' in name: + form = '双盘' + elif '三盘' in name: + form = '三盘' + elif '四盘' in name: + form = '四盘' + + # System USB + if '16G' in name.upper() or '16g' in name: + if 'windows' in name.lower(): + return 'USB:16GB:windows' + elif '飞牛' in name or '蜗牛' in name: + return 'USB:16GB:飞牛' + return f'USB:16GB:{name[:20]}' + + return f"{cpu}:{mem}:{form}" + + +def clean_sku_name(name): + """Clean up OCR artifacts in SKU name""" + name = name.strip() + # Remove known noise + name = re.sub(r'关注券5元优惠券', '', name) + name = re.sub(r'店铺关注券5元优惠券', '', name) + name = re.sub(r'[>\s]+$', '', name) + name = re.sub(r'^[>\s]+', '', name) + name = re.sub(r'\s+', ' ', name) + return name + + +# Collect all unique SKUs +all_entries = [] +for img_name, lines in all_results.items(): + entries = extract_sku_v4(lines) + all_entries.extend(entries) + +# Deduplicate - keep highest confidence per normalized key +dedup = {} +for e in all_entries: + key = normalize_sku_key(e['sku']) + if key and key not in dedup: + dedup[key] = e + elif key and e['confidence'] > dedup[key]['confidence']: + dedup[key] = e + +# Sort by price +sorted_entries = sorted(dedup.values(), key=lambda x: x['price']) + +# Print for debugging +print("=" * 60) +print("Extracted unique SKUs:") +for i, e in enumerate(sorted_entries): + key = normalize_sku_key(e['sku']) + print(f" [{key}] {clean_sku_name(e['sku'])} -> ${e['price']} (orig: ${e['original_price']})") + +# Build markdown +md = [] +md.append("# SKU 售价汇总") +md.append("") +md.append("> 从拼多多商家后台「商品价格管理」截图 OCR 识别提取") +md.append(f"> 提取日期: 2026-06-16") +md.append(f"> 图片数量: {len(all_results)} 张") +md.append(f"> 识别 SKU: {len(sorted_entries)} 个(已去重合并)") +md.append("") +md.append("---") +md.append("") + +# Group by platform +groups = { + '系统U盘 (16GB)': [], + '单盘位 - 1037U': [], + '双盘位 - 1037U': [], + '单盘位 - J1900': [], + '双盘位 - J1900': [], + '单盘位 - J3160': [], + '双盘位 - J3160': [], + '单盘位 - i3': [], + '双盘位 - i3': [], + '双盘位 - i5': [], + '三盘位/四盘位 - i3/i5': [], + '5005U 系列': [], + '其他NAS/黑裙晖': [], +} + +for e in sorted_entries: + name = e['sku'] + key = normalize_sku_key(name) + + if 'USB' in key: + groups['系统U盘 (16GB)'].append(e) + elif '1037U' in key: + if '单盘' in key: + groups['单盘位 - 1037U'].append(e) + else: + groups['双盘位 - 1037U'].append(e) + elif 'J1900' in key: + if '单盘' in key: + groups['单盘位 - J1900'].append(e) + else: + groups['双盘位 - J1900'].append(e) + elif 'J3160' in key: + if '单盘' in key: + groups['单盘位 - J3160'].append(e) + else: + groups['双盘位 - J3160'].append(e) + elif 'I3' in key: + if '三盘' in key or '四盘' in key: + groups['三盘位/四盘位 - i3/i5'].append(e) + elif '单盘' in key: + groups['单盘位 - i3'].append(e) + else: + groups['双盘位 - i3'].append(e) + elif 'I5' in key: + if '三盘' in key or '四盘' in key: + groups['三盘位/四盘位 - i3/i5'].append(e) + else: + groups['双盘位 - i5'].append(e) + elif '5005U' in key: + groups['5005U 系列'].append(e) + else: + groups['其他NAS/黑裙晖'].append(e) + +for group_name in groups: + entries = groups[group_name] + if not entries: + continue + + md.append(f"## {group_name}") + md.append("") + md.append("| SKU 配置 | 券后售价 (¥) | 券前原价 (¥) | 优惠 |") + md.append("|---------|------------:|------------:|-----|") + + for e in entries: + name = clean_sku_name(e['sku']) + md.append(f"| {name} | {e['price']:.2f} | {e['original_price']:.2f} | -5.00 |") + + md.append("") + +md.append("---") +md.append("") +md.append("## 说明") +md.append("") +md.append("- **优惠**: 统一使用店铺关注券 -5.00 元") +md.append("- **数据来源**: 拼多多商家后台「商品价格管理」页面截图") +md.append("- **识别工具**: RapidOCR (rapidocr-onnxruntime)") +md.append("- **注意事项**: OCR 识别的 SKU 名称可能有碎片化/错字,建议对比后台实际数据确认") +md.append("- **价格日期**: 2026-06-05 (截图时间)") +md.append("") + +# Fix the price summary line +overall_min = min(e['price'] for e in sorted_entries) if sorted_entries else 0 +overall_max = max(e['price'] for e in sorted_entries) if sorted_entries else 0 +md.append(f"> 价格范围: ¥{overall_min:.2f} ~ ¥{overall_max:.2f}") + +output_path = r"D:\workspace\membank\售价\SKU售价汇总.md" +with open(output_path, "w", encoding="utf-8") as f: + f.write("\n".join(md)) + +print(f"\n\nSaved to: {output_path}") +print(f"Total unique SKUs: {len(sorted_entries)}") diff --git a/.trae/skills/ocr-extract/run_ocr.py b/.trae/skills/ocr-extract/run_ocr.py new file mode 100644 index 0000000..d445865 --- /dev/null +++ b/.trae/skills/ocr-extract/run_ocr.py @@ -0,0 +1,54 @@ +from rapidocr_onnxruntime import RapidOCR +from PIL import Image +import os, time, json + +ocr = RapidOCR() +img_dir = r"D:\workspace\membank\售价" +images = sorted([f for f in os.listdir(img_dir) if f.lower().endswith(('.jpeg', '.jpg', '.png'))]) + +all_results = {} + +for img_name in images: + img_path = os.path.join(img_dir, img_name) + img = Image.open(img_path) + w, h = img.size + print(f"\n=== {img_name} ({w}x{h}) ===") + + # Use chunking for images taller than 2000px + if h > 2000: + chunk_size = 1200 + all_text = [] + for i in range(0, h, chunk_size): + bottom = min(i + chunk_size, h) + chunk = img.crop((0, i, w, bottom)) + tmp_path = os.path.join(os.environ['TEMP'], f'ocr_chunk_{i}.png') + chunk.save(tmp_path) + result, _ = ocr(tmp_path) + if result: + for line in result: + text = line[1] + conf = line[2] + bbox = line[0] + y_center = (bbox[0][1] + bbox[2][1]) / 2 + i # absolute y position + all_text.append((y_center, text, conf)) + if os.path.exists(tmp_path): + os.remove(tmp_path) + # Sort by vertical position + all_text.sort(key=lambda x: x[0]) + lines = [(t[1], t[2]) for t in all_text] + else: + result, _ = ocr(img_path) + if result: + lines = [(line[1], line[2]) for line in result] + else: + lines = [] + + all_results[img_name] = lines + for text, conf in lines: + print(f" [{conf}] {text}") + +# Save raw results as JSON for later processing +with open(os.path.join(img_dir, "ocr_results.json"), "w", encoding="utf-8") as f: + json.dump(all_results, f, ensure_ascii=False, indent=2) + +print("\n\n=== OCR完成,结果已保存到 ocr_results.json ===") diff --git a/.trae/skills/sku-designer/principles/fyd-nas.md b/.trae/skills/sku-designer/principles/fyd-nas.md new file mode 100644 index 0000000..90c277e --- /dev/null +++ b/.trae/skills/sku-designer/principles/fyd-nas.md @@ -0,0 +1,132 @@ +# 飞牛 NAS 产品设计原则 + +## 基础版-金 + +### 机箱与CPU对应 +- 2.5金-单(单SATA口):amd-a4, n2840, n2930, j1800 +- 3.5金属大-双(双SATA口):1037u +- 3.5金属小-单(单SATA口):amd-a4, n2840, n2930, j1800 +- 3.5金属小-双(双SATA口):1037u + +### 内存硬盘 +- 4G-32G + +### 建议零售价 +- 成本+30 + +## 基础版-亚 + +### 机箱与CPU对应 +- 亚克力-双(双SATA口):1037u +- 亚克力-单(单SATA口):amd-a4, n2840, n2930, j1800 + +### 内存硬盘 +- 4G-32G + +### 建议零售价 +- 成本+30 + +## 金属机箱 3.5寸大 + +### 机箱类型 +- 3盘版(单SATA口CPU):3855U, J1900, J3160 +- 4盘版(双SATA口CPU):1037U, 3217U, 5005U + +### 内存硬盘可用 +- 4G-32G, 4G-64G, 8G-64G, 8G-128G + +## 金属机箱 3.5寸小 + +### 机箱类型 +- 3.5+2.5盘位(双SATA口):1037U + +### 内存硬盘 +- 4G-32G + +### 数据盘可选 +- 500G, 1T, 3T, 4T, 6T + +### 建议零售价 +- 主机(无硬盘):+50 +- 主机+500G:+80 +- 主机+1T:+90 +- 主机+3T:+100 +- 主机+4T/6T:+110 + +## 亚克力机箱-单 配置原则 + +### 主板要求 +- 可安装单SATA口主板 + +### 可用CPU +- AMD-A4,J1900,J3160 + +### 可用内存 +- D3-2G,D3-4G + +### 可用系统盘 +- mSATA-16G,mSATA-32G,mSATA-64G,mSATA-128G + +### 推荐价格 +- 配2G内存:成本+20 +- 配4G内存:成本+30 + +## 亚克力机箱-双 配置原则 + +### 主板要求 +- 可安装双SATA口主板 + +### 可用CPU +- 1037U,3217U,5005U + +### 可用内存 +- 2G-16G,2G-32G,4G-64G,4G-128G,8G-128G + +## 高性能版-亚-双-J1900 + +### 机箱 +- 亚克力-双机箱 + +### CPU +- J1900(单SATA口,需双SATA转换器) + +### 内存硬盘可用 +- 4G-32G, 4G-64G, 8G-64G, 8G-128G + +### 建议零售价 +- 成本+50 + +## 高性能版-亚-双-J3160 + +### 机箱 +- 亚克力-双机箱 + +### CPU +- J3160(单SATA口,需双SATA转换器) + +### 内存硬盘可用 +- 4G-32G, 4G-64G, 8G-64G, 8G-128G + +### 建议零售价 +- 成本+70 + +## 高性能-i5-i7 + +### 可用机箱 +- 亚克力机箱-双 +- 金属机箱3.5寸大 + +### CPU i5 +- 可用:i5-2代,i5-3代,i5-4代 + +### CPU i7 +- 可用:i7-2代,i7-3代,i7-4代 + +### 内存硬盘 +- i5:4G-128G,8G-128G +- i7统一:8G-128G + +### 建议零售价 +- 成本+80 + +## 金属机箱3.5寸大-三或四盘版 \ No newline at end of file diff --git a/.trae/skills/sku-designer/principles/guajibao.md b/.trae/skills/sku-designer/principles/guajibao.md new file mode 100644 index 0000000..0bfb512 --- /dev/null +++ b/.trae/skills/sku-designer/principles/guajibao.md @@ -0,0 +1,36 @@ +# 挂机宝产品设计原则 + +## 概述 + +挂机宝分为一拖一和一拖三两种规格 + +## 一拖一 + +### 可选CPU +- 1037u, i3-3217u, i3-5005u, 3855u + +### 可选内存-系统盘 +- 4G-64G, 4G-128G, 8G-128G + +### 可选硬盘 +- HD 500G + +### 机箱 +- 2.5寸金属机箱 + +### 可选 +- 无线网卡 + +## 一拖三 + +### 可选CPU +- 1037u, i3-3217u, i3-5005u, 3855u + +### 可选内存-系统盘 +- 4G-64G, 4G-128G, 8G-128G + +### 机箱 +- 一拖三亚克力机箱 + +### 可选 +- 无线网卡 diff --git a/.trae/skills/sku-designer/principles/win-host.md b/.trae/skills/sku-designer/principles/win-host.md new file mode 100644 index 0000000..53412f9 --- /dev/null +++ b/.trae/skills/sku-designer/principles/win-host.md @@ -0,0 +1,31 @@ +# Win 小主机产品设计原则 + +## 通用规格 + +- win小主机全部使用2.5寸机箱 + +## 龙虾盒子 + +### 可用CPU +- i3-5005u, 3855u + +### 内存硬盘可使用 +- 4G-64G, 4G-128G, 8G-128G + +### 标配 +- 无线网卡 + +### 可选 +- HD 500G硬盘 + +## Win10 小主机 + +### 可用CPU +- 1037u, i3-3217u, i3-5005u, 3855u + +### 内存硬盘可使用 +- 4G-64G, 4G-128G, 8G-128G + +### 可选 +- 无线网卡 +- HD 500G硬盘 diff --git a/aio-mcp/.gitignore b/aio-mcp/.gitignore new file mode 100644 index 0000000..0747dda --- /dev/null +++ b/aio-mcp/.gitignore @@ -0,0 +1,12 @@ +# 构建产物 +/aio-mcp +/aio-mcp.exe +/dist/ +*.exe + +# 运行时数据 +/data/ +*.db +*.db-journal +*.db-wal +*.db-shm diff --git a/aio-mcp/README.md b/aio-mcp/README.md new file mode 100644 index 0000000..0e3a53a --- /dev/null +++ b/aio-mcp/README.md @@ -0,0 +1,100 @@ +# aio-mcp + +aio-system 的自定义 MCP 服务器(Go)。作为 AI Agent 的「手」,以 MCP 协议对外暴露工具;后续的 SQLite / 文件 / 打印 / 检测等自定义工具都往这里加。 + +基于官方 SDK:[github.com/modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk)(v1.7.0)。 + +## 目录结构 + +``` +aio-mcp/ +├── main.go # 入口:选择 stdio / http 传输 +└── internal/ + ├── server/ + │ └── server.go # 服务器装配(创建实例 + 注册工具) + └── tools/ + ├── registry.go # 工具注册总表 RegisterAll + ├── ping.go # 示例工具:健康检查(无参数) + └── echo.go # 示例工具:参数传递(有参数) +``` + +## 运行 + +```bash +go build -o aio-mcp.exe . # 构建 + +# stdio 模式(默认,供本地 Agent 拉起,如 Reasonix / Claude) +aio-mcp.exe + +# http 模式(远程客户端访问) +aio-mcp.exe -transport http -addr localhost:8000 +``` + +依赖拉取走内网代理,若需直连官方源:`go env -w GOPROXY=https://proxy.golang.org,direct`。 + +## 新增一个工具 + +1. 在 `internal/tools/` 新建文件(如 `weather.go`),用参数 struct + 泛型 `mcp.AddTool` 注册: + + ```go + package tools + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + type weatherArgs struct { + City string `json:"city" jsonschema:"城市名,必填"` + Days int `json:"days,omitempty" jsonschema:"预报天数,默认 1"` + } + + func registerWeather(s *mcp.Server) { + mcp.AddTool(s, &mcp.Tool{ + Name: "weather", + Description: "查询某城市未来几天的天气。", + }, func(_ context.Context, _ *mcp.CallToolRequest, args weatherArgs) (*mcp.CallToolResult, any, error) { + // ...业务逻辑... + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: "晴,25℃"}}, + }, nil, nil + }) + } + ``` + +2. 在 `internal/tools/registry.go` 的 `RegisterAll` 中追加一行:`registerWeather(s)`。 + +工具需要共享状态(SQLite 连接等)时,参考 `registry.go` 顶部注释,把 `RegisterAll` 演进为带 `deps` 参数。 + +## 冒烟测试 + +stdio 模式用换行分隔的 JSON-RPC 直接喂协议消息: + +```bash +printf '%s\n' \ +'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}' \ +'{"jsonrpc":"2.0","method":"notifications/initialized"}' \ +'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \ +'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"你好","times":2}}}' \ +| aio-mcp.exe +``` + +http 模式: + +```bash +curl -X POST http://localhost:8000/mcp -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0.0.1"}}}' +``` + +## 接入 Agent(以 Reasonix / Claude 为例) + +在客户端 MCP 配置中注册,指向构建产物: + +```json +{ "mcpServers": { "aio-mcp": { "command": "D:\\workspace\\membank\\aio-mcp\\aio-mcp.exe" } } } +``` + +## 注意事项 + +- stdio 模式下 **stdout 是协议通道**:日志已固定输出到 stderr,业务代码请勿往 stdout 打印。 +- 工具名只允许 `a-z A-Z 0-9 _ - .`。 diff --git a/aio-mcp/go.mod b/aio-mcp/go.mod new file mode 100644 index 0000000..8f426b9 --- /dev/null +++ b/aio-mcp/go.mod @@ -0,0 +1,16 @@ +module aio-mcp + +go 1.25.1 + +require github.com/modelcontextprotocol/go-sdk v1.7.0 + +require ( + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.4 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/time v0.15.0 // indirect +) diff --git a/aio-mcp/go.sum b/aio-mcp/go.sum new file mode 100644 index 0000000..f93aba1 --- /dev/null +++ b/aio-mcp/go.sum @@ -0,0 +1,24 @@ +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= diff --git a/aio-mcp/internal/server/server.go b/aio-mcp/internal/server/server.go new file mode 100644 index 0000000..349d09d --- /dev/null +++ b/aio-mcp/internal/server/server.go @@ -0,0 +1,22 @@ +// Package server 负责 MCP 服务器的装配:创建 server 实例并注册全部工具。 +package server + +import ( + "github.com/modelcontextprotocol/go-sdk/mcp" + + "aio-mcp/internal/tools" +) + +// New 创建并装配 aio-mcp 服务器。 +// version 为服务器实现版本号,随 MCP initialize 握手上报给客户端。 +func New(version string) *mcp.Server { + s := mcp.NewServer(&mcp.Implementation{ + Name: "aio-mcp", + Version: version, + }, nil) + + // 注册全部工具。新增工具后在 tools.RegisterAll 里追加即可。 + tools.RegisterAll(s) + + return s +} diff --git a/aio-mcp/internal/tools/echo.go b/aio-mcp/internal/tools/echo.go new file mode 100644 index 0000000..9fe9d04 --- /dev/null +++ b/aio-mcp/internal/tools/echo.go @@ -0,0 +1,40 @@ +package tools + +import ( + "context" + "fmt" + "strings" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// echoArgs 是 echo 工具的输入参数。 +// json tag 决定参数名;jsonschema tag 提供描述,供客户端(LLM)理解参数。 +type echoArgs struct { + Message string `json:"message" jsonschema:"要回显的文本,必填"` + Times int `json:"times,omitempty" jsonschema:"回显次数,默认 1"` +} + +// registerEcho 注册 echo 工具:演示带参数的工具写法。 +func registerEcho(s *mcp.Server) { + mcp.AddTool(s, &mcp.Tool{ + Name: "echo", + Description: "把传入的文本原样回显指定次数,用于演示参数传递与工具调用。", + }, func(_ context.Context, _ *mcp.CallToolRequest, args echoArgs) (*mcp.CallToolResult, any, error) { + times := args.Times + if times <= 0 { + times = 1 + } + // 用换行分隔,避免重复文本粘连。 + line := strings.TrimRight(args.Message, "\n") + lines := make([]string, times) + for i := range lines { + lines[i] = line + } + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: fmt.Sprintf("%s\n(共 %d 次)", strings.Join(lines, "\n"), times)}, + }, + }, nil, nil + }) +} diff --git a/aio-mcp/internal/tools/ping.go b/aio-mcp/internal/tools/ping.go new file mode 100644 index 0000000..6550cb3 --- /dev/null +++ b/aio-mcp/internal/tools/ping.go @@ -0,0 +1,22 @@ +package tools + +import ( + "context" + "time" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// registerPing 注册 ping 工具:无参数健康检查,返回 pong 与服务器时间。 +func registerPing(s *mcp.Server) { + mcp.AddTool(s, &mcp.Tool{ + Name: "ping", + Description: "健康检查。返回 pong 与服务器当前时间,用于确认 aio-mcp 连接正常。", + }, func(_ context.Context, _ *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "pong " + time.Now().Format(time.RFC3339)}, + }, + }, nil, nil + }) +} diff --git a/aio-mcp/internal/tools/registry.go b/aio-mcp/internal/tools/registry.go new file mode 100644 index 0000000..ba9f04e --- /dev/null +++ b/aio-mcp/internal/tools/registry.go @@ -0,0 +1,26 @@ +// Package tools 集中管理 aio-mcp 的全部 MCP 工具。 +// +// # 新增工具的固定步骤 +// +// 1. 在本目录新建一个文件(如 weather.go),实现 +// func registerXXX(s *mcp.Server) +// 2. 在 RegisterAll 中追加一行 registerXXX(s) +// +// 工具通过泛型 mcp.AddTool[In, Out] 注册:输入参数用 struct 定义, +// 加 json 与 jsonschema tag(描述、必填),SDK 会自动生成 inputSchema +// 并做输入校验,无需手工写 JSON Schema。 +// +// 后续工具需要共享状态(SQLite 连接、配置、http client 等)时, +// 把 RegisterAll 演进为 RegisterAll(s *mcp.Server, deps *Deps), +// 在 server.New 里构造 deps 传入即可,各工具文件只改签名。 +package tools + +import ( + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// RegisterAll 注册所有工具。 +func RegisterAll(s *mcp.Server) { + registerPing(s) + registerEcho(s) +} diff --git a/aio-mcp/main.go b/aio-mcp/main.go new file mode 100644 index 0000000..3d1012c --- /dev/null +++ b/aio-mcp/main.go @@ -0,0 +1,53 @@ +// aio-mcp:aio-system 的自定义 MCP 服务器。 +// +// 作为 AI Agent 的「手」,以 MCP 协议对外暴露工具。 +// 默认以 stdio 传输运行(本地 Agent 直接拉起),也可切换为 +// streamable HTTP 供远程客户端访问。 +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "net/http" + "os" + + "github.com/modelcontextprotocol/go-sdk/mcp" + + "aio-mcp/internal/server" +) + +// version 可用 -ldflags "-X main.version=..." 在构建时覆盖。 +var version = "0.1.0" + +func main() { + transport := flag.String("transport", "stdio", "传输方式:stdio(默认,供本地 Agent 拉起)或 http") + addr := flag.String("addr", "localhost:8000", "http 模式下的监听地址") + flag.Parse() + + // 日志统一走 stderr:stdio 模式下 stdout 是 MCP 协议通道,绝不能混入日志。 + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil))) + + svr := server.New(version) + + switch *transport { + case "stdio": + slog.Info("aio-mcp starting", "transport", "stdio") + // server.Run 会阻塞,直到 stdin 关闭或出错。 + if err := svr.Run(context.Background(), &mcp.StdioTransport{}); err != nil { + slog.Error("stdio server failed", "err", err) + os.Exit(1) + } + case "http": + handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return svr }, nil) + slog.Info("aio-mcp starting", "transport", "http", "addr", *addr) + if err := http.ListenAndServe(*addr, handler); err != nil { + slog.Error("http server failed", "err", err) + os.Exit(1) + } + default: + fmt.Fprintf(os.Stderr, "未知 transport: %q(支持: stdio, http)\n", *transport) + os.Exit(2) + } +} diff --git a/auto-check/.codewhale/state/subagents.v1.lock b/auto-check/.codewhale/state/subagents.v1.lock deleted file mode 100644 index e69de29..0000000 diff --git a/auto-check/.gitignore b/auto-check/.gitignore index 60d857d..0ff346d 100644 --- a/auto-check/.gitignore +++ b/auto-check/.gitignore @@ -2,6 +2,11 @@ reports/ *.exe *.test +run.log +run.err + +# 编译产物 +/auto-check # 导出的镜像文件(体积大,不入库;维护时重新 docker save 生成) docker/stress-test/*.tar @@ -9,3 +14,4 @@ docker/stress-test/*.tar # IDE .idea/ .vscode/ +.codewhale/ diff --git a/auto-check/README.md b/auto-check/README.md index 290cdb1..04b01cb 100644 --- a/auto-check/README.md +++ b/auto-check/README.md @@ -55,7 +55,7 @@ go build -o auto-check . ### `run` / `stress` | 标志 | 默认值 | 说明 | |------|--------|------| -| `--stress` | `cpu,memory` | 压测类型: `cpu`, `memory`, `disk`, `memnative`, `full` | +| `--stress` | `cpu,memory` | 压测类型: `cpu`, `memory`, `disk`, `memnative`, `fnstable`, `full` | | `--duration` | `30s` | 持续时间 | | `--threads` | `4` | 线程数 | | `--ips` | _(必填,仅stress)_ | 目标 IP (逗号分隔) | @@ -68,8 +68,24 @@ go build -o auto-check . | `memory` | stress-ng | 内存压测 | | `disk` | stress-ng | 磁盘IO混合压测 | | `memnative` | stressapptest | 内存稳定性精压(数据完整性校验) | +| `fnstable` | fnscript | 稳定性门禁:社区脚本 self_inspection.py 先行检测硬件(stress CPU + memtester 内存校验),通过后自有压测再跑;未通过则自有压测全部跳过 | | `full` | stress-ng | CPU+内存+磁盘 三合一综合压测 | +## 稳定性门禁(fnstable) + +基于飞牛社区 [fnscript](https://gitee.com/xiao-zhu245/fnscript)(MIT)的 `self_inspection.py`,vendor 原样收录在 `docker/stress-test/vendor/fnscript/`,源码不做修改: + +1. 门禁先跑社区脚本:CPU 稳定性(`stress`)+ 内存数据校验(`memtester` 可用内存 97% 单遍,30-120 分钟) +2. 门禁通过 → 自有压测工具(stress-ng/fio 等)再跑一遍,按自有格式出报告 +3. 门禁失败 → 自有压测全部 skip,报告仍完整输出(硬件已有问题无需再跑) + +适配说明(不改社区脚本源码的代价,已在适配层 `scripts/fnstable.sh` 规避): +- 脚本 CLI 模式无论成败 exit code 均为 0,结果按 stdout 关键词("未发现问题")判定 +- `--memory` 有 y/n 交互确认,用 `printf 'y\ny\n'` 管道喂入 +- 依赖(python3/psutil/memtester/stress/sudo)预装在压测镜像中 + +配置(`auto-check.yaml`):`stress.fn_memory` 控制门禁是否执行 memtester 内存校验(默认 true,快速验证可设 false)。 + ## 压测执行方式(Docker 容器) 压测统一在 Docker 容器中执行:工具与测试脚本预装在镜像里,无需在目标机在线安装,环境一致性好(飞牛 NAS 默认已安装 Docker)。 @@ -80,13 +96,15 @@ auto-check/ ├── docker/ # Docker 镜像维护目录(独立) │ └── stress-test/ │ ├── Dockerfile # 镜像定义 +│ ├── build.sh # 构建 + 导出 + 同步配置(时间戳 tag) │ ├── scripts/ # 测试脚本(打包进镜像) -│ │ ├── run.sh # 入口脚本(环境变量驱动) +│ │ ├── run.sh # 入口脚本(环境变量驱动 + 门禁调度) +│ │ ├── fnstable.sh # 稳定性门禁适配器(fnscript 社区脚本 → 结构化协议) │ │ ├── cpu.sh # CPU 压测 │ │ ├── memory.sh # 内存压测 │ │ ├── disk.sh # 磁盘压测 │ │ └── ... -│ └── README.md +│ └── vendor/fnscript/ # 社区脚本原样收录(self_inspection.py,MIT) └── pkg/stress/ └── runner.go # 核心逻辑:送镜像 + 驱动容器测试 ``` @@ -97,24 +115,71 @@ auto-check/ 3. 启动容器执行测试(脚本在镜像内,环境变量传参) 4. 解析容器输出 → 生成报告 +### 容器内执行流程(run.sh) +1. 系统信息(`===SYSTEM_INFO===`) +2. 温度监控后台采样(`temp_start.sh`) +3. 稳定性门禁(`fnstable.sh`,types 含 `fnstable` 时):fnscript 社区脚本先行检测硬件稳定性,未通过则后续自有压测全部 skip +4. 各项压测:先单项(cpu/memory/disk/memnative)、综合(full)最后 +5. 温度监控收尾(`===MONITOR:temp===`) +6. 内核日志(`===MONITOR:dmesg===`,`-T` 带时间戳)+ 结束标记 + ### 配置方式(在 `auto-check.yaml` 中) ```yaml stress: - docker_image: "stress-test:latest" # 压测镜像(飞牛 NAS 默认支持 Docker) + docker_image: "stress-test:20260824-153045" # 压测镜像 tag(build.sh 自动维护) docker_tar: "docker/stress-test/stress-test.tar" # 本地镜像文件,目标机无镜像时自动上传并 load docker_args: "--network=host --privileged" ``` +### 容器运行时环境变量 +| 变量 | 说明 | 默认值 | +|------|------|--------| +| `AUTOCHECK_TYPES` | 测试类型(逗号分隔) | `cpu,memory` | +| `AUTOCHECK_DURATION` | 持续时间(秒) | `30` | +| `AUTOCHECK_THREADS` | 线程数 | `4` | +| `AUTOCHECK_MEM_SIZE_MB` | 内存压测大小 | `0` (自动) | +| `AUTOCHECK_DISK_SIZE_MB` | 磁盘压测大小 | `1024` | +| `AUTOCHECK_DISK_DIR` | 磁盘压测目录 | `/tmp` | +| `AUTOCHECK_TEMP_INTERVAL` | 温度采样间隔 | `10` | +| `AUTOCHECK_SAMPLE_INTERVAL` | 运行时采样间隔 | `2` | +| `AUTOCHECK_FN_MEMORY` | fnstable 门禁是否执行 memtester 内存校验 | `1` | + +### 容器运行时挂载(由 auto-check 自动添加) +| 挂载 | 作用 | +|------|------| +| `-v /sys:/sys:ro` | 容器内是独立 sysfs,挂载后才能读到宿主机硬件传感器(温度采集依赖此项) | +| `-v /etc/localtime:/etc/localtime:ro` | 容器时区与宿主机一致,dmesg 时间戳与采样时间才准确 | + +> 注意:镜像内已设置 `LC_ALL=C.UTF-8`。若 locale 为纯 C,`sensors` 会输出 "+51.0 C"(无 ° 符号),导致温度正则失配、采集不到数据。 + ### 构建镜像(维护时使用) +固定分发模式:不依赖 registry,镜像以 tar 文件形式放在工程目录。推荐用 `build.sh`(自动时间戳 tag): + ```bash cd docker/stress-test -docker build -t stress-test:latest . -# 导出到工程目录,供测试时上传分发(不依赖 registry) -docker save -o stress-test.tar stress-test:latest +./build.sh # 自动取当前时间戳(YYYYMMDD-HHMMSS) +./build.sh 20260824-153045 # 手动指定时间戳(重打/复现) ``` -### 详细文档 -镜像维护说明请参考:[docker/stress-test/README.md](docker/stress-test/README.md) +`build.sh` 会同时完成: +1. 用时间戳 tag 构建镜像(不保留 `latest`,`docker images` 可区分每次构建) +2. 导出两份 tar:带时间戳归档 `stress-test-<时间戳>.tar` + 同步覆盖 `stress-test.tar`(auto-check 默认引用) +3. 自动更新 `auto-check.yaml` 的 `docker_image` 字段,确保配置与新 tag 同步 + +手动构建(不推荐)需自行同步 `auto-check.yaml` 的 `docker_image`;旧时间戳镜像可 `docker rmi stress-test:<旧时间戳>` 清理。 + +### 镜像内预装工具 +| 工具 | 用途 | +|------|------| +| `stress-ng` | CPU/内存/磁盘压测 | +| `fio` | 磁盘 IO 测试 | +| `lm-sensors` | 温度监控 | +| `sysstat` | 系统性能统计 | +| `procps` | 进程监控 | +| `python3` + `psutil` | fnscript 稳定性门禁运行环境 | +| `memtester` | fnstable 门禁内存数据校验 | +| `stress` | fnstable 门禁 CPU 压测 | +| `sudo` | fnscript 脚本内部调用 `sudo memtester`(容器以 root 运行,无密码) | ## 项目结构 diff --git a/auto-check/auto-check b/auto-check/auto-check deleted file mode 100644 index 860bf9d..0000000 Binary files a/auto-check/auto-check and /dev/null differ diff --git a/auto-check/auto-check.yaml b/auto-check/auto-check.yaml index 86de0ab..37e6f6e 100644 --- a/auto-check/auto-check.yaml +++ b/auto-check/auto-check.yaml @@ -19,7 +19,9 @@ ssh: # --- 压力测试 --- stress: - types: "cpu,memory,disk,full" # 单项在前、综合(full)最后 + # fnstable(fnscript 社区稳定性脚本)放在最前作门禁:先检测硬件真实稳定性, + # 通过后自有压测再跑一遍出报告;未通过则自有压测全部跳过 + types: "fnstable,cpu,memory,disk,full" # 单项在前、综合(full)最后 duration: 30s threads: 4 mem_size_mb: 0 # 内存压测占用(MB),0=自动(可用内存60%) @@ -27,6 +29,8 @@ stress: disk_dir: "" # 磁盘压测目录,空=自动临时目录 temp_interval: 10s # 温度采样间隔 temp_limit: 90 # 温度上限(°C),超过判失败,0=不判定 + fn_memory: true # fnstable 门禁的 memtester 内存校验(可用内存97%单遍, + # 耗时 30-120 分钟按内存大小定;false=仅 CPU 门禁,快速出报告) # --- Docker 镜像(压测统一在容器中执行,工具与脚本预装在镜像里)--- # 固定分发模式:目标机无镜像时,上传本地导出的 tar 并 docker load(不依赖 registry) docker_image: "stress-test:20260824-144244" # 压测镜像名,镜像维护见 docker/stress-test/ diff --git a/auto-check/docker/stress-test/Dockerfile b/auto-check/docker/stress-test/Dockerfile index 722c274..2a35eaf 100644 --- a/auto-check/docker/stress-test/Dockerfile +++ b/auto-check/docker/stress-test/Dockerfile @@ -32,20 +32,34 @@ FROM debian:12-slim ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 # 清理缓存,减少镜像体积 +# - python3/python3-psutil/memtester/stress/sudo:fnscript 稳定性门禁的依赖 +# (self_inspection.py 调 psutil 计算测试内存、sudo memtester 做数据校验、 +# stress 做 CPU 压测;脚本原样收录在 vendor/fnscript/,源码不做修改) RUN apt-get update && apt-get install -y --no-install-recommends \ stress-ng \ fio \ lm-sensors \ sysstat \ procps \ + python3 \ + python3-psutil \ + memtester \ + stress \ + sudo \ && rm -rf /var/lib/apt/lists/* # 验证工具安装 -RUN stress-ng --version && fio --version && sensors --version +RUN stress-ng --version && fio --version && sensors --version \ + && python3 -c "import psutil; print('psutil', psutil.__version__)" \ + && command -v memtester && command -v stress && command -v sudo # 复制测试脚本到镜像中 COPY scripts/ /scripts/ +# fnscript 稳定性测试脚本(社区脚本 vendor 收录,源码不改,见 vendor/fnscript/README.md) +COPY vendor/fnscript/script/self_inspection.py /vendor/fnscript/self_inspection.py +COPY vendor/fnscript/LICENSE /vendor/fnscript/LICENSE + # 设置脚本执行权限 RUN chmod +x /scripts/*.sh diff --git a/auto-check/docker/stress-test/README.md b/auto-check/docker/stress-test/README.md deleted file mode 100644 index 5aec851..0000000 --- a/auto-check/docker/stress-test/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# stress-test 镜像 - -压力测试镜像,为 auto-check 提供预装压测工具与测试脚本的容器环境。压测统一在容器内执行,无需在目标机在线安装任何依赖。 - -## 工作流程 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ auto-check 工作流程 │ -│ │ -│ 1. 检测目标机器的 Docker 可用性 │ -│ 2. 检查镜像是否存在(不存在则上传本地导出的 tar 并 docker load)│ -│ 3. 启动容器执行测试(脚本已打包在镜像中) │ -│ 4. 收集测试结果并解析 │ -│ 5. 生成报告 │ -└─────────────────────────────────────────────────────────────┘ -``` - -## 优势 - -| 特性 | 说明 | -|------|------| -| 工具获取 | 预装在镜像中,无需在线安装 | -| 网络依赖 | 镜像已缓存后目标机无需联网 | -| 环境一致性 | 统一的容器环境,不受目标机发行版差异影响 | - -## 构建与导出(维护时使用) - -固定分发模式:不依赖 registry,镜像以 tar 文件形式放在工程目录, -目标机无镜像时由 auto-check 自动上传并 `docker load`。 - -### 推荐:使用构建脚本(自动时间戳 tag) - -`build.sh` 会以 `YYYYMMDD-HHMMSS` 格式自动生成时间戳 tag(如 `stress-test:20260824-153045`), -便于在 `docker images` 中一眼区分新旧版本。脚本会同时: - -1. 用时间戳 tag 构建镜像(**不再保留 `latest`**) -2. 导出两份 tar:带时间戳的归档 + 同步覆盖 `stress-test.tar`(auto-check 默认引用) -3. 自动更新 `auto-check.yaml` 的 `docker_image` 字段,确保配置与新 tag 同步 - -```bash -cd docker/stress-test -./build.sh # 自动取当前时间戳 -./build.sh 20260824-153045 # 手动指定时间戳(重打/复现) -``` - -产物: - -| 文件 | 用途 | -|------|------| -| 镜像 `stress-test:<时间戳>` | 唯一 tag,`docker images` 可看到每次构建 | -| Tar `stress-test-<时间戳>.tar` | 带时间戳归档,保留历史版本 | -| Tar `stress-test.tar` | auto-check 默认引用,脚本自动覆盖为最新 | -| `auto-check.yaml` | `docker_image` 自动同步为新 tag | - -> 旧时间戳镜像可手动 `docker rmi stress-test:<旧时间戳>` 清理。 - -### 手动构建(不推荐) - -如需绕过脚本手动构建,必须自行同步 `auto-check.yaml`: - -```bash -cd docker/stress-test -docker build -t stress-test:20260824-153045 . # 必须带时间戳 -docker save -o stress-test.tar stress-test:20260824-153045 -# 然后编辑 ../../auto-check.yaml,把 docker_image 改成新 tag -``` - -> 注意:手动构建如忘记带时间戳,重复构建后会丢失版本信息,调试时易混淆。 - -## 使用 - -在 `auto-check.yaml` 中配置(`docker_image` 由 `build.sh` 自动维护): - -```yaml -stress: - docker_image: "stress-test:20260824-153045" # 每次构建自动更新为新时间戳 - docker_tar: "docker/stress-test/stress-test.tar" - docker_args: "--network=host --privileged" -``` - -## 环境变量 - -容器运行时通过以下环境变量传入参数: - -| 变量 | 说明 | 默认值 | -|------|------|--------| -| `AUTOCHECK_TYPES` | 测试类型(逗号分隔) | `cpu,memory` | -| `AUTOCHECK_DURATION` | 持续时间(秒) | `30` | -| `AUTOCHECK_THREADS` | 线程数 | `4` | -| `AUTOCHECK_MEM_SIZE_MB` | 内存压测大小 | `0` (自动) | -| `AUTOCHECK_DISK_SIZE_MB` | 磁盘压测大小 | `1024` | -| `AUTOCHECK_DISK_DIR` | 磁盘压测目录 | `/tmp` | -| `AUTOCHECK_TEMP_INTERVAL` | 温度采样间隔 | `10` | -| `AUTOCHECK_SAMPLE_INTERVAL` | 运行时采样间隔 | `2` | - -## 容器内执行流程(run.sh) - -1. 系统信息(`===SYSTEM_INFO===`) -2. 温度监控后台采样(`temp_start.sh`) -3. 各项压测:先单项(cpu/memory/disk/memnative)、综合(full)最后 -4. 温度监控收尾(`===MONITOR:temp===`) -5. 内核日志(`===MONITOR:dmesg===`,`-T` 带时间戳)+ 结束标记 - -## 容器运行时挂载(由 auto-check 自动添加) - -| 挂载 | 作用 | -|------|------| -| `-v /sys:/sys:ro` | 容器内是独立 sysfs,挂载后才能读到宿主机硬件传感器(温度采集依赖此项) | -| `-v /etc/localtime:/etc/localtime:ro` | 容器时区与宿主机一致,dmesg 时间戳与采样时间才准确 | - -> 注意:镜像内已设置 `LC_ALL=C.UTF-8`。若 locale 为纯 C,`sensors` 会输出 "+51.0 C"(无 ° 符号),导致温度正则失配、采集不到数据。 - -## 镜像内容 - -``` -/stress-test/ -├── Dockerfile # 镜像定义 -├── scripts/ # 测试脚本 -│ ├── run.sh # 入口脚本 -│ ├── cpu.sh # CPU 压测 -│ ├── memory.sh # 内存压测 -│ ├── disk.sh # 磁盘压测 -│ ├── full.sh # 综合压测 -│ ├── memnative.sh # 内存精压 -│ ├── sysinfo.sh # 系统信息 -│ ├── temp_start.sh # 温度监控启动 -│ ├── temp_end.sh # 温度监控收尾 -│ └── dmesg.sh # 内核日志 -└── README.md # 本文档 -``` - -## 依赖工具 - -| 工具 | 用途 | -|------|------| -| `stress-ng` | CPU/内存/磁盘压测 | -| `fio` | 磁盘 IO 测试 | -| `lm-sensors` | 温度监控 | -| `sysstat` | 系统性能统计 | -| `procps` | 进程监控 | \ No newline at end of file diff --git a/auto-check/docker/stress-test/scripts/fnstable.sh b/auto-check/docker/stress-test/scripts/fnstable.sh new file mode 100644 index 0000000..d7b8e90 --- /dev/null +++ b/auto-check/docker/stress-test/scripts/fnstable.sh @@ -0,0 +1,81 @@ +# fnstable.sh - fnscript 稳定性门禁(社区脚本 self_inspection.py,vendor 原样收录不改源码) +# +# 思路:先用社区脚本检测硬件真实稳定性(stress CPU 压测 + memtester 内存数据校验), +# 通过后自有压测工具再跑一遍出报告;不通过则 run.sh 跳过自有压测。 +# +# 适配注意(不改源码的代价,已逐一规避): +# 1. self_inspection.py CLI 模式无论成败 exit code 均为 0, +# 只能按 stdout 关键词判定结果("未发现问题" = 通过)。 +# 2. --memory 内含两处 y/n 交互确认(input()),非交互环境会 EOFError, +# 用 printf 'y\ny\n' 管道喂入(两处确认都覆盖)。 +# 3. 脚本内部调 sudo memtester / stress,依赖已在镜像中预装(root 下 sudo 直接可用)。 +# 4. memtester 测试可用内存的 97%,单遍耗时 30-120 分钟(由内存大小决定), +# 可用 AUTOCHECK_FN_MEMORY=0 关闭内存校验只做 CPU 门禁。 +# +# 输出遵守 auto-check 结构化协议(===TEST:fnstable=== 块), +# 并设置 GATE_PASSED 供 run.sh 决定是否继续自有压测。 + +echo "===TEST:fnstable===" + +FN_SCRIPT="/vendor/fnscript/self_inspection.py" +FN_DURATION="${AUTOCHECK_DURATION:-30}" +FN_MEMORY="${AUTOCHECK_FN_MEMORY:-1}" + +FN_ALL_PASSED=1 + +START_TIME=$(date +%s%N) + +# ---- 1. CPU 稳定性(fnscript: stress --cpu 4 --timeout N)---- +set +e +FN_CPU_OUT=$(python3 "$FN_SCRIPT" --cpu "$FN_DURATION" 2>&1) +set -e +if printf '%s' "$FN_CPU_OUT" | grep -q "未发现问题"; then + FN_CPU_STATUS="pass" +else + FN_CPU_STATUS="fail" + FN_ALL_PASSED=0 +fi + +# ---- 2. 内存稳定性(fnscript: sudo memtester <可用内存97%> 1,数据校验)---- +FN_MEM_STATUS="skip" +FN_MEM_NOTE="未启用(AUTOCHECK_FN_MEMORY=0)" +if [ "$FN_MEMORY" = "1" ]; then + FN_MEM_NOTE="" + set +e + FN_MEM_OUT=$(printf 'y\ny\n' | python3 "$FN_SCRIPT" --memory 2>&1) + set -e + if printf '%s' "$FN_MEM_OUT" | grep -q "未发现问题"; then + FN_MEM_STATUS="pass" + else + FN_MEM_STATUS="fail" + FN_ALL_PASSED=0 + fi +fi + +END_TIME=$(date +%s%N) +DURATION_MS=$(( (END_TIME - START_TIME) / 1000000 )) + +# ---- 3. 汇总结果(门禁语义:任一子项失败即整体失败)---- +if [ "$FN_ALL_PASSED" -eq 1 ]; then + echo 'status:pass' +else + echo 'status:fail' +fi +echo "duration:${DURATION_MS}ms" + +echo "output:[CPU] ${FN_CPU_STATUS} (stress ${FN_DURATION}s)" +printf '%s\n' "$FN_CPU_OUT" | while IFS= read -r line; do + [ -n "$line" ] && echo "output: $line" +done +if [ "$FN_MEM_STATUS" = "skip" ]; then + echo "output:[MEM] skip - ${FN_MEM_NOTE}" +else + echo "output:[MEM] ${FN_MEM_STATUS} (memtester 可用内存97% 单遍校验)" + printf '%s\n' "$FN_MEM_OUT" | while IFS= read -r line; do + [ -n "$line" ] && echo "output: $line" + done +fi +echo '' + +# 供 run.sh 门禁判断(fnstable 失败 → 跳过自有压测) +GATE_PASSED=$FN_ALL_PASSED diff --git a/auto-check/docker/stress-test/scripts/run.sh b/auto-check/docker/stress-test/scripts/run.sh index 2c16f73..8da814d 100644 --- a/auto-check/docker/stress-test/scripts/run.sh +++ b/auto-check/docker/stress-test/scripts/run.sh @@ -28,7 +28,7 @@ source /scripts/temp_start.sh # 未知类型输出 skip,保证报告完整 for T in $(echo "$AUTOCHECK_TYPES" | tr ',' ' '); do case "$T" in - cpu|memory|disk|memnative|full) ;; + cpu|memory|disk|memnative|full|fnstable) ;; *) echo "===TEST:$T===" echo "status:skip" @@ -38,16 +38,37 @@ for T in $(echo "$AUTOCHECK_TYPES" | tr ',' ' '); do esac done -# 3. 按固定顺序执行请求的测试(先单项、综合 full 最后) +# 3. 稳定性门禁(fnscript 社区脚本先行,不通过则跳过自有压测) +# fnstable.sh 内部设置 GATE_PASSED(1=通过 / 0=失败) +GATE_PASSED=1 REQ_TYPES=",${AUTOCHECK_TYPES// /}," -for TYPE in cpu memory disk memnative full; do - case "$REQ_TYPES" in - *",$TYPE,"*) source "/scripts/$TYPE.sh" ;; - esac -done +case "$REQ_TYPES" in + *,fnstable,*) source /scripts/fnstable.sh ;; +esac -# 4. 温度监控收尾 +# 4. 按固定顺序执行请求的自有测试(先单项、综合 full 最后) +# 门禁未通过 → 硬件已有问题,自有压测全部 skip(报告仍完整输出) +if [ "$GATE_PASSED" -eq 1 ]; then + for TYPE in cpu memory disk memnative full; do + case "$REQ_TYPES" in + *",$TYPE,"*) source "/scripts/$TYPE.sh" ;; + esac + done +else + for TYPE in cpu memory disk memnative full; do + case "$REQ_TYPES" in + *",$TYPE,"*) + echo "===TEST:$TYPE===" + echo "status:skip" + echo "output:fnscript 稳定性门禁未通过,跳过自有压测" + echo "" + ;; + esac + done +fi + +# 5. 温度监控收尾 source /scripts/temp_end.sh -# 5. 内核日志 + 结束标记 +# 6. 内核日志 + 结束标记 source /scripts/dmesg.sh diff --git a/auto-check/docker/stress-test/vendor/fnscript/LICENSE b/auto-check/docker/stress-test/vendor/fnscript/LICENSE new file mode 100644 index 0000000..2ab8c4c --- /dev/null +++ b/auto-check/docker/stress-test/vendor/fnscript/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 又菜又爱玩的小朱猪 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/auto-check/docker/stress-test/vendor/fnscript/README.md b/auto-check/docker/stress-test/vendor/fnscript/README.md new file mode 100644 index 0000000..99f32b6 --- /dev/null +++ b/auto-check/docker/stress-test/vendor/fnscript/README.md @@ -0,0 +1,54 @@ +# 2.0版本重磅来袭 +### ✅脚本菜单改进 +![image](https://github.com/user-attachments/assets/efed14a9-c2a1-475e-8c67-cdd42dc0c95c) \ +![image](https://github.com/user-attachments/assets/66b43e51-0eb5-41b8-8770-6484e953afda) +### 🖱TUI交互式界面,感觉就像在使用Windows +![output](https://github.com/user-attachments/assets/f0a63758-8174-408d-94a0-bbe637bd1bd2) +### 终端绑定功能,进入root自动运行脚本 +![output](https://github.com/user-attachments/assets/f330ba45-3a7d-4bbf-bd0a-84530d0e1663) + +### ⬇git下载和更新 +✅ 操作简单,维护更新方便,采用Gitee国内源,确保全国地区均可访问 +### 📄独立脚本文件 +✅继续沿用1.0脚本分开管理,便于维护 +### MIT最宽松的开源协议 + + +# 如何使用 +## TUI交互式运行 +所有脚本目前均已实现图形化TUI交互式操作,您可以直接使用一键使用命令后根据菜单提示选择对应功能,默认使用的是TUI。由于脚本TUI采用的是 `textual` 库,这个库无法通过apt进行安装和管理,所以必须强行通过pip来进行安装(曾考虑过进行打包后执行但部分脚本可能存在打包后运行问题) +[点击这里查阅详细冲突风险以及解决方案](https://github.com/yxsj245/fnscript/blob/2.0/pip%E5%BA%93%E5%86%B2%E7%AA%81%E9%A3%8E%E9%99%A9.md) +### 使用运行命令 +```bash +git clone https://gitee.com/xiao-zhu245/fnscript.git +cd fnscript/ +python3 menu.py +``` +### 建议使用 1panel面板终端或者CMD终端进行TUI操作,其它软件的终端可能存在兼容问题。使用脚本建议进入root用户下运行。 + +## 传统命令行运行 +若您担心pip库冲突风险或无法使用交互式终端情况下,我将部分功能单一的脚本增加了传统通过命令行形式运行的功能,在无需安装`textual`库就可以直接运行使用 +### 如何使用 +您可以通过此命令拉取任意脚本后直接使用 +```bash +python3 此处替换脚本名称 -h #获取参数信息 +``` +# 脚本信息 + +| 脚本名称 | 作用 | 是否支持TUI | 是否支持命令行 | +| ------- | ------- | ------- | ------- | +| cdmount.py | 挂载CD/DVD |✅|❎| +| filestorage.py | 分析目录文件大小 |✅|❎| +| Powermanagement.py | 电源管理 |✅|✅| +| qcowtools.py | qcow2转换工具 |✅|✅| +| self_inspection.py | 硬件压测 |✅|✅| +| startIOMMU.py | 开启IOMMU直通 |✅|✅| +| swapinfo.py | 管理swap |✅|✅| +| vmtoolses.py | 安装虚拟机工具 |✅|✅| +| WOLstart.py | 开启WOL网络唤醒 |✅|✅| +| network_diagnostic_tool.py | 网络诊断与修改 |✅|✅| +| raid_repair_tui.py | 阵列状态和修复 |✅|❎| +| ffmpeg_converter_tui.py | 影音万能格式转换 |✅|❎| + +# 其它合作脚本 +[七月七夕纯命令行传统脚本](https://github.com/qiyueqixi/fnos) diff --git a/auto-check/docker/stress-test/vendor/fnscript/script/self_inspection.py b/auto-check/docker/stress-test/vendor/fnscript/script/self_inspection.py new file mode 100644 index 0000000..afe351a --- /dev/null +++ b/auto-check/docker/stress-test/vendor/fnscript/script/self_inspection.py @@ -0,0 +1,757 @@ +#!/usr/bin/env python +import os +import subprocess +import psutil +import time +import signal +import sys +import argparse + +# 尝试导入textual库,如果不存在则设置标志 +HAS_TEXTUAL = True +try: + from textual.app import App, ComposeResult + from textual.widgets import Header, Footer, Static, Button, Label, Input + from textual.containers import Container, Horizontal, Vertical + from textual.screen import Screen, ModalScreen + import asyncio +except ImportError: + HAS_TEXTUAL = False + # 如果没有textual但尝试无参数运行,我们需要提示用户 + if len(sys.argv) == 1: + print("错误: 未安装textual库,无法启动图形界面。") + print("您可以通过命令行参数使用此脚本的核心功能:") + print(" 内存测试: python self_inspection.py --memory") + print(" CPU测试: python self_inspection.py --cpu [测试时长(秒)]") + print(" 查看帮助: python self_inspection.py -h") + sys.exit(1) + +# 定义颜色常量(保留以兼容原始代码) +RESET = "\033[0m" +BOLD = "\033[1m" +RED = "\033[31m" +GREEN = "\033[32m" +YELLOW = "\033[33m" +BLUE = "\033[34m" +MAGENTA = "\033[35m" +CYAN = "\033[36m" +WHITE = "\033[37m" + +# 全局变量存储当前运行的进程 +current_process = None + +# 工具函数 +def clear_screen(): + """清空终端屏幕""" + os.system('clear' if os.name == 'posix' else 'cls') + +def get_test_memory(): + """计算可用于测试的内存 (剩余内存的 97%)""" + available_memory = psutil.virtual_memory().available // (1024 * 1024) # 获取可用内存 (MB) + return int(available_memory * 0.97) # 取 97% 进行测试 + +def check_memory_usage(): + """检查当前内存使用情况""" + return psutil.virtual_memory().percent + +def run_command(command): + """执行命令并返回结果""" + global current_process + + result = { + 'status_code': 0, + 'stdout': '', + 'stderr': '' + } + + try: + # 保存进程引用以便后续可以终止 + current_process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 + ) + + # 等待进程完成 + stdout, stderr = current_process.communicate() + + # 获取返回码 + result['status_code'] = current_process.returncode + result['stdout'] = stdout + result['stderr'] = stderr + + # 清除当前进程引用 + current_process = None + + return result + except Exception as e: + if current_process: + try: + current_process.terminate() + except: + pass + current_process = None + + result['status_code'] = 1 + result['stderr'] = str(e) + return result + +def stop_current_process(): + """停止当前正在运行的进程""" + global current_process + if current_process: + try: + current_process.terminate() + # 给进程一点时间来终止 + time.sleep(0.5) + # 如果进程还在运行,强制结束 + if current_process.poll() is None: + current_process.kill() + return True + except Exception: + return False + finally: + current_process = None + return False + +# 命令行直接执行内存测试的函数 +def cli_memory_test(): + """通过命令行直接执行内存测试""" + memory_usage = check_memory_usage() + + if memory_usage > 80: + print(f"警告: 当前系统内存使用率已达 {memory_usage}%!") + print("建议关闭一些软件后再运行,继续运行可能检测效果不够准确") + print("是否继续? (y/n)") + response = input().strip().lower() + if response != 'y': + print("操作已取消") + return False + + print("建议关闭所有docker容器以及停用所有应用程序以保证检测的准确性。") + print("是否继续? (y/n)") + response = input().strip().lower() + if response != 'y': + print("操作已取消") + return False + + mem_size = f"{get_test_memory()}M" + print("正在运行内存测试,请勿进行其它操作,如果运行过程中出现死机重启则大概率为内存问题。") + print("整个测试过程预计 30-120 分钟 具体根据内存大小,如果需要终止测试请按Ctrl+C。") + + try: + # 设置信号处理器以便可以通过Ctrl+C终止测试 + def signal_handler(sig, frame): + print("\n测试已被用户终止") + stop_current_process() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + # 执行测试 + result = run_command(["sudo", "memtester", mem_size, "1"]) + + if result['status_code'] == 0: + print("内存测试完成!未发现问题。") + return True + else: + print(f"内存测试失败!错误信息:{result['stderr']}") + return False + + except Exception as e: + print(f"测试过程中发生错误: {str(e)}") + return False + +# 命令行直接执行CPU测试的函数 +def cli_cpu_test(duration=60): + """通过命令行直接执行CPU测试 + + Args: + duration (int): 测试持续时间(秒),默认为60秒 + """ + try: + duration = int(duration) + if duration <= 0: + print("错误: 测试时长必须大于0") + return False + except ValueError: + print("错误: 测试时长必须是一个有效的整数") + return False + + print(f"正在进行 CPU 压测,持续 {duration} 秒...如需提前结束测试,请按Ctrl+C。") + + try: + # 设置信号处理器以便可以通过Ctrl+C终止测试 + def signal_handler(sig, frame): + print("\n测试已被用户终止") + stop_current_process() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + # 执行测试 + result = run_command(["stress", "--cpu", "4", "--timeout", str(duration)]) + + if result['status_code'] == 0: + print("CPU 压测完成!未发现问题。") + return True + else: + print(f"CPU 压测失败!错误信息:{result['stderr']}") + return False + + except Exception as e: + print(f"测试过程中发生错误: {str(e)}") + return False + +def parse_arguments(): + """解析命令行参数""" + parser = argparse.ArgumentParser(description='系统健康检测工具') + parser.add_argument('--memory', action='store_true', + help='执行内存测试') + parser.add_argument('--cpu', nargs='?', const=60, type=int, + help='执行CPU压测,可选参数为测试时长(秒),默认为60秒') + return parser.parse_args() + +# 只有在导入了textual库的情况下才定义这些类 +if HAS_TEXTUAL: + # 确认对话框 + class ConfirmDialog(ModalScreen): + """确认对话框""" + + def __init__(self, message: str, action_callback=None): + super().__init__() + self.message = message + self.action_callback = action_callback + + def compose(self) -> ComposeResult: + with Container(id="dialog-container"): + yield Label(f"[yellow]{self.message}[/yellow]", id="dialog-title") + with Horizontal(id="dialog-buttons"): + yield Button("确认", id="confirm", variant="primary") + yield Button("取消", id="cancel", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "confirm" and self.action_callback: + self.dismiss(True) + self.action_callback() + else: + self.dismiss(False) + + # 成功提示对话框 + class SuccessDialog(ModalScreen): + """成功提示对话框""" + + def __init__(self, message: str): + super().__init__() + self.message = message + + def compose(self) -> ComposeResult: + with Container(id="dialog-container"): + yield Label(f"[green]{self.message}[/green]", id="dialog-title") + with Horizontal(id="dialog-buttons"): + yield Button("确定", id="ok", variant="primary") + + def on_button_pressed(self, event: Button.Pressed) -> None: + self.dismiss(True) + + # 错误提示对话框 + class ErrorDialog(ModalScreen): + """错误提示对话框""" + + def __init__(self, message: str): + super().__init__() + self.message = message + + def compose(self) -> ComposeResult: + with Container(id="dialog-container"): + yield Label(f"[red]{self.message}[/red]", id="dialog-title") + with Horizontal(id="dialog-buttons"): + yield Button("确定", id="ok", variant="primary") + + def on_button_pressed(self, event: Button.Pressed) -> None: + self.dismiss(True) + + # 输入对话框 + class InputDialog(ModalScreen): + """输入对话框""" + + def __init__(self, message: str, default_value: str = "", action_callback=None): + super().__init__() + self.message = message + self.default_value = default_value + self.action_callback = action_callback + + def compose(self) -> ComposeResult: + with Container(id="dialog-container"): + yield Label(f"[blue]{self.message}[/blue]", id="dialog-title") + yield Input(value=self.default_value, id="input-field") + with Horizontal(id="dialog-buttons"): + yield Button("确认", id="confirm", variant="primary") + yield Button("取消", id="cancel", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "confirm" and self.action_callback: + input_value = self.query_one("#input-field", Input).value + self.dismiss(input_value) + self.action_callback(input_value) + else: + self.dismiss(None) + + class SystemHealthApp(App): + CSS = """ + Screen { + background: $surface; + } + + #title { + dock: top; + text-align: center; + text-style: bold; + background: $accent; + color: $text; + padding: 1; + margin-bottom: 1; + } + + #main-container { + width: 100%; + height: auto; + padding: 1; + } + + #menu-container { + width: 100%; + height: auto; + margin: 1; + border: solid $primary; + padding: 1; + } + + .menu-button { + margin: 1; + min-width: 30; + min-height: 3; + background: $primary; + color: $text; + border: tall $primary-lighten-2; + } + + #memory-test { + background: $success-darken-1; + color: $text; + border: tall $success; + } + + #cpu-test { + background: $warning-darken-1; + color: $text; + border: tall $warning; + } + + #stop-test { + background: $error-darken-1; + color: $text; + border: tall $error; + margin: 1; + min-width: 30; + min-height: 3; + } + + #status { + height: auto; + min-height: 3; + margin: 1; + padding: 1; + border: solid $accent; + background: $surface-darken-1; + } + + /* 对话框样式 */ + #dialog-container { + width: 60%; + height: auto; + padding: 2; + background: $surface; + border: thick $accent; + margin: 1 0; + } + + /* 成功对话框样式 */ + SuccessDialog #dialog-container { + border: thick $success; + } + + /* 错误对话框样式 */ + ErrorDialog #dialog-container { + border: thick $error; + } + + /* 确认对话框样式 */ + ConfirmDialog #dialog-container { + border: thick $warning; + } + + /* 输入对话框样式 */ + InputDialog #dialog-container { + border: thick $primary; + } + + #dialog-title { + text-align: center; + width: 100%; + margin-bottom: 2; + } + + #input-field { + margin-bottom: 1; + } + + #dialog-buttons { + align: center middle; + width: 100%; + } + + #dialog-buttons Button { + margin: 0 2; + min-width: 10; + } + + /* 按钮样式增强 */ + Button { + background: $primary; + border: tall $primary-lighten-2; + padding: 1 2; + } + + Button:hover { + background: $primary-lighten-1; + } + + Button#confirm { + background: $success-darken-1; + border: tall $success; + } + + Button#confirm:hover { + background: $success; + } + + Button#cancel, Button#stop-test { + background: $error-darken-1; + border: tall $error; + } + + Button#cancel:hover, Button#stop-test:hover { + background: $error; + } + + Button#ok { + background: $primary-darken-1; + border: tall $primary; + } + + Button#ok:hover { + background: $primary; + } + """ + + BINDINGS = [ + ("q", "quit", "退出"), + ("escape", "quit", "退出"), + ("ctrl+c", "stop_test", "停止测试"), + ] + + def __init__(self): + super().__init__() + self.is_testing = False + + def compose(self) -> ComposeResult: + yield Header(show_clock=True) + yield Footer() + + with Container(id="main-container"): + yield Label("[b]系统健康检测工具[/b]", id="title") + + with Container(id="menu-container"): + yield Button("内存检测 (使用 memtester)", id="memory-test", classes="menu-button") + yield Button("CPU 压测", id="cpu-test", classes="menu-button") + yield Button("停止当前测试", id="stop-test", disabled=True) + + # 状态输出 + yield Static("准备就绪,请选择检测项目。", id="status") + + def on_button_pressed(self, event: Button.Pressed) -> None: + """按钮点击事件处理""" + button_id = event.button.id + + if button_id == "memory-test": + self.start_memory_test() + elif button_id == "cpu-test": + self.start_cpu_test() + elif button_id == "stop-test": + self.stop_test() + + def action_stop_test(self) -> None: + """键盘快捷键停止测试""" + self.stop_test() + + def stop_test(self) -> None: + """停止当前测试""" + if not self.is_testing: + return + + status = self.query_one("#status") + status.update("[yellow]正在停止测试...[/yellow]") + + # 停止当前进程 + if stop_current_process(): + status.update("[green]测试已停止。[/green]") + self.show_success_dialog("测试已成功停止。") + else: + status.update("[red]无法停止测试,可能测试已经完成。[/red]") + + # 更新界面状态 + self.is_testing = False + + # 使用try/except捕获可能的NoMatches异常 + try: + stop_button = self.query_one("#stop-test", Button) + if stop_button: + stop_button.disabled = True + + memory_button = self.query_one("#memory-test", Button) + if memory_button: + memory_button.disabled = False + + cpu_button = self.query_one("#cpu-test", Button) + if cpu_button: + cpu_button.disabled = False + except Exception as e: + # 记录错误但不中断程序 + print(f"更新按钮状态时出错: {str(e)}") + + def start_memory_test(self) -> None: + """开始内存测试""" + status = self.query_one("#status") + memory_usage = check_memory_usage() + + if memory_usage > 80: + warning_message = f"警告: 当前系统内存使用率已达 {memory_usage}%!\n建议关闭一些软件后再运行,继续运行可能检测效果不够准确" + status.update(f"[yellow]{warning_message}[/yellow]") + + # 显示确认对话框 + def confirm_callback(): + self.show_memory_test_warning() + + self.push_screen(ConfirmDialog(warning_message, confirm_callback)) + else: + self.show_memory_test_warning() + + def show_memory_test_warning(self): + """显示内存测试警告""" + warning = "建议关闭所有docker容器以及停用所有应用程序以保证检测的准确性。" + status = self.query_one("#status") + status.update(f"[red]{warning}[/red]") + + def confirm_callback(): + self.run_memory_test() + + self.push_screen(ConfirmDialog(warning, confirm_callback)) + + def run_memory_test(self) -> None: + """执行内存测试""" + status = self.query_one("#status") + mem_size = f"{get_test_memory()}M" + + message = "正在运行内存测试,请勿进行其它操作,如果运行过程中出现死机重启则大概率为内存问题。整个测试过程预计 30-120 分钟 具体根据内存大小,如果需要终止测试请按Ctrl+C或点击停止按钮。" + status.update(f"[blue]{message}[/blue]") + + # 更新界面状态 + self.is_testing = True + + # 使用try/except捕获可能的NoMatches异常 + try: + stop_button = self.query_one("#stop-test", Button) + if stop_button: + stop_button.disabled = False + + memory_button = self.query_one("#memory-test", Button) + if memory_button: + memory_button.disabled = True + + cpu_button = self.query_one("#cpu-test", Button) + if cpu_button: + cpu_button.disabled = True + except Exception as e: + # 记录错误但不中断程序 + print(f"更新按钮状态时出错: {str(e)}") + + # 创建异步任务 + asyncio.create_task(self.do_memory_test(mem_size)) + + async def do_memory_test(self, mem_size): + """异步执行内存测试""" + status = self.query_one("#status") + + try: + # 在异步中执行测试 + result = await asyncio.to_thread(run_command, ["sudo", "memtester", mem_size, "1"]) + + if result['status_code'] == 0: + success_message = "内存测试完成!未发现问题。" + status.update(f"[green]{success_message}[/green]") + self.show_success_dialog(success_message) + else: + # 检查是否是被用户手动终止的 + if current_process is None and not result['stderr']: + status.update("[yellow]测试已被用户终止。[/yellow]") + else: + error_message = f"内存测试失败!错误信息:{result['stderr']}" + status.update(f"[red]{error_message}[/red]") + self.show_error_dialog(error_message) + + except Exception as e: + error_message = f"测试过程中发生错误: {str(e)}" + status.update(f"[red]{error_message}[/red]") + self.show_error_dialog(error_message) + finally: + # 恢复界面状态 + self.is_testing = False + + # 使用try/except捕获可能的NoMatches异常 + try: + stop_button = self.query_one("#stop-test", Button) + if stop_button: + stop_button.disabled = True + + memory_button = self.query_one("#memory-test", Button) + if memory_button: + memory_button.disabled = False + + cpu_button = self.query_one("#cpu-test", Button) + if cpu_button: + cpu_button.disabled = False + except Exception as e: + # 记录错误但不中断程序 + print(f"更新按钮状态时出错: {str(e)}") + + def start_cpu_test(self) -> None: + """开始CPU测试""" + def input_callback(duration): + if duration and duration.isdigit() and int(duration) > 0: + self.run_cpu_test(duration) + else: + self.show_error_dialog("请输入有效的时间(秒)!") + + self.push_screen(InputDialog("请输入 CPU 压测时长 (秒):", "60", input_callback)) + + def run_cpu_test(self, duration): + """执行CPU测试""" + status = self.query_one("#status") + + message = f"正在进行 CPU 压测,持续 {duration} 秒...如需提前结束测试,请按Ctrl+C或点击停止按钮。" + status.update(f"[blue]{message}[/blue]") + + # 更新界面状态 + self.is_testing = True + + # 使用try/except捕获可能的NoMatches异常 + try: + stop_button = self.query_one("#stop-test", Button) + if stop_button: + stop_button.disabled = False + + memory_button = self.query_one("#memory-test", Button) + if memory_button: + memory_button.disabled = True + + cpu_button = self.query_one("#cpu-test", Button) + if cpu_button: + cpu_button.disabled = True + except Exception as e: + # 记录错误但不中断程序 + print(f"更新按钮状态时出错: {str(e)}") + + # 创建异步任务 + asyncio.create_task(self.do_cpu_test(duration)) + + async def do_cpu_test(self, duration): + """异步执行CPU测试""" + status = self.query_one("#status") + + try: + # 在异步中执行测试 + result = await asyncio.to_thread(run_command, ["stress", "--cpu", "4", "--timeout", duration]) + + if result['status_code'] == 0: + success_message = "CPU 压测完成!未发现问题。" + status.update(f"[green]{success_message}[/green]") + self.show_success_dialog(success_message) + else: + # 检查是否是被用户手动终止的 + if current_process is None and not result['stderr']: + status.update("[yellow]测试已被用户终止。[/yellow]") + else: + error_message = f"CPU 压测失败!错误信息:{result['stderr']}" + status.update(f"[red]{error_message}[/red]") + self.show_error_dialog(error_message) + + except Exception as e: + error_message = f"测试过程中发生错误: {str(e)}" + status.update(f"[red]{error_message}[/red]") + self.show_error_dialog(error_message) + finally: + # 恢复界面状态 + self.is_testing = False + + # 使用try/except捕获可能的NoMatches异常 + try: + stop_button = self.query_one("#stop-test", Button) + if stop_button: + stop_button.disabled = True + + memory_button = self.query_one("#memory-test", Button) + if memory_button: + memory_button.disabled = False + + cpu_button = self.query_one("#cpu-test", Button) + if cpu_button: + cpu_button.disabled = False + except Exception as e: + # 记录错误但不中断程序 + print(f"更新按钮状态时出错: {str(e)}") + + def show_success_dialog(self, message: str) -> None: + """显示成功对话框""" + self.push_screen(SuccessDialog(message)) + + def show_error_dialog(self, message: str) -> None: + """显示错误对话框""" + self.push_screen(ErrorDialog(message)) + +if __name__ == "__main__": + args = parse_arguments() + + # 如果指定了命令行参数,则直接执行相应功能 + if args.memory: + cli_memory_test() + sys.exit(0) + + if args.cpu is not None: + cli_cpu_test(args.cpu) + sys.exit(0) + + # 如果没有指定命令行参数,则启动图形界面(如果textual可用) + if HAS_TEXTUAL: + app = SystemHealthApp() + app.run() + else: + # 这种情况不应该发生,因为在导入时已经处理了,但为了代码完整性保留 + print("错误: 未安装textual库,无法启动图形界面。") + print("您可以通过命令行参数使用此脚本的核心功能:") + print(" 内存测试: python self_inspection.py --memory") + print(" CPU测试: python self_inspection.py --cpu [测试时长(秒)]") + print(" 查看帮助: python self_inspection.py -h") + sys.exit(1) \ No newline at end of file diff --git a/auto-check/pkg/config/config.go b/auto-check/pkg/config/config.go index c0aba4d..3e367c2 100644 --- a/auto-check/pkg/config/config.go +++ b/auto-check/pkg/config/config.go @@ -51,6 +51,7 @@ type StressConfig struct { DiskDir string `yaml:"disk_dir"` // 磁盘压测目录,空=自动临时目录 TempInterval time.Duration `yaml:"temp_interval"` // 温度采样间隔,0=默认10s TempLimit float64 `yaml:"temp_limit"` // 温度上限(°C),0=不判定 + FnMemory bool `yaml:"fn_memory"` // fnstable 门禁的 memtester 内存校验(30-120 分钟) // 压测统一在 Docker 容器中执行(工具与脚本预装在镜像里) DockerImage string `yaml:"docker_image"` // 压测镜像名称,如 stress-test:latest DockerTar string `yaml:"docker_tar"` // 本地导出的镜像文件(docker save),目标机无镜像时上传并 load @@ -110,6 +111,7 @@ func Default() *Config { DiskSizeMB: 1024, TempInterval: 10 * time.Second, TempLimit: 90, + FnMemory: true, DockerImage: "stress-test:latest", DockerTar: "docker/stress-test/stress-test.tar", DockerArgs: "--network=host --privileged", diff --git a/auto-check/pkg/sshclient/sshclient.go b/auto-check/pkg/sshclient/sshclient.go index 304f589..86f988c 100644 --- a/auto-check/pkg/sshclient/sshclient.go +++ b/auto-check/pkg/sshclient/sshclient.go @@ -46,6 +46,20 @@ func Connect(ip string, cfg *Config) (*ssh.Client, error) { if err != nil { return nil, fmt.Errorf("SSH 连接 %s 失败: %w", addr, err) } + + // KeepAlive:长时压测(如 fnstable 门禁的 memtester 可达 30-120 分钟)期间 + // 连接上无数据流动,定期发 keepalive 请求防止 NAT/防火墙掐断空闲 TCP 连接。 + // 连接关闭后 SendRequest 返回错误,goroutine 自行退出。 + go func() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + for range ticker.C { + if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil { + return + } + } + }() + return client, nil } diff --git a/auto-check/pkg/stress/metrics.go b/auto-check/pkg/stress/metrics.go index 141d4d8..8ee8a87 100644 --- a/auto-check/pkg/stress/metrics.go +++ b/auto-check/pkg/stress/metrics.go @@ -36,6 +36,8 @@ func BuildMetrics(types []TestType) []Metric { m.Tool = "stressapptest" case TestFull: m.Tool = "stress-ng" + case TestFnStable: + m.Tool = "fnscript" default: m.Enabled = false m.DisableReason = "未知测试类型" @@ -64,6 +66,7 @@ type Config struct { DiskDir string // 空=自动临时目录 TempLogInt time.Duration TempLimit float64 // 温度上限(°C),0=不判定 + FnMemory bool // fnstable 门禁是否执行 memtester 内存校验(30-120 分钟) // 压测统一在 Docker 容器中执行(工具预装在镜像里) DockerImage string // 压测镜像名称,如 stress-test:20260824-153045(由 build.sh 自动维护) DockerTar string // 本地导出的镜像文件(docker save),目标机无镜像时上传并 load diff --git a/auto-check/pkg/stress/runner.go b/auto-check/pkg/stress/runner.go index 4feacba..263652a 100644 --- a/auto-check/pkg/stress/runner.go +++ b/auto-check/pkg/stress/runner.go @@ -304,6 +304,7 @@ func (r *Runner) runInDocker() (string, error) { "-e AUTOCHECK_DISK_DIR=%s "+ "-e AUTOCHECK_TEMP_INTERVAL=%d "+ "-e AUTOCHECK_SAMPLE_INTERVAL=2 "+ + "-e AUTOCHECK_FN_MEMORY=%d "+ "%s %s", containerName, r.cfg.Types, @@ -313,6 +314,7 @@ func (r *Runner) runInDocker() (string, error) { r.cfg.DiskSizeMB, r.cfg.DiskDir, int(r.cfg.TempLogInt.Seconds()), + boolToInt(r.cfg.FnMemory), r.cfg.DockerArgs, r.cfg.DockerImage) @@ -403,6 +405,7 @@ func TestDevice(ip string, cfg config.Config) *Report { stressCfg.DiskDir = cfg.Stress.DiskDir stressCfg.TempLogInt = cfg.Stress.TempInterval stressCfg.TempLimit = cfg.Stress.TempLimit + stressCfg.FnMemory = cfg.Stress.FnMemory // Docker 镜像配置(工具与脚本预装在镜像中,本地导出的 tar 上传到目标机后 load) stressCfg.DockerImage = cfg.Stress.DockerImage stressCfg.DockerTar = cfg.Stress.DockerTar @@ -453,6 +456,14 @@ func queryRemoteMAC(client *ssh.Client) string { // 工具函数 // ============================ +// boolToInt bool → 0/1(Docker 环境变量传参用) +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} + // IsPassed 报告是否通过 func IsPassed(rpt *Report) bool { return rpt != nil && rpt.Failed == 0 && rpt.Errors == 0 diff --git a/auto-check/pkg/stress/types.go b/auto-check/pkg/stress/types.go index 1d44dfa..e73eeb1 100644 --- a/auto-check/pkg/stress/types.go +++ b/auto-check/pkg/stress/types.go @@ -15,6 +15,7 @@ const ( TestDiskIO TestType = "disk" TestMemNative TestType = "memnative" TestFull TestType = "full" + TestFnStable TestType = "fnstable" // fnscript 稳定性门禁(社区脚本先行,通过后自有压测再跑) MonitorTemp TestType = "temp" MonitorDmesg TestType = "dmesg" ) diff --git a/auto-check/run.err b/auto-check/run.err deleted file mode 100644 index e69de29..0000000 diff --git a/auto-check/run.log b/auto-check/run.log deleted file mode 100644 index 024baf9..0000000 --- a/auto-check/run.log +++ /dev/null @@ -1,148 +0,0 @@ - [配置] 加载完成: 网段=10.0.3.0/24 间隔=3m0s 压测=cpu,memory,disk,full -══ 工作流启动(间隔 3m0s,设备以 IP 为 key)══ - -════ 轮次开始 [01:18:28] ════ -[扫描] 网段 10.0.3.0/24,共 254 个IP,10 并发 ping 探测... -[扫描] 完成,发现 13 台存活设备 - [扫描] 设备 13 台 - [测试] 10.0.3.171 () 开始压测... - [测试] 10.0.3.171 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.171:22 失败: dial tcp 10.0.3.171:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.175 () 开始压测... - [测试] 10.0.3.175 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.175:22 失败: dial tcp 10.0.3.175:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.236 () 开始压测... - [测试] 10.0.3.236 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.236:22 失败: dial tcp 10.0.3.236:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.4 () 开始压测... - [测试] 10.0.3.4 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.4:22 失败: dial tcp 10.0.3.4:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.1 () 开始压测... - [测试] 10.0.3.1 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.1:22 失败: dial tcp 10.0.3.1:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.104 () 开始压测... - [测试] 10.0.3.104 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.104:22 失败: dial tcp 10.0.3.104:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.123 () 开始压测... - [测试] 10.0.3.123 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.123:22 失败: dial tcp 10.0.3.123:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.119 () 开始压测... - [测试] 10.0.3.119 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.119:22 失败: dial tcp 10.0.3.119:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.147 () 开始压测... - [测试] 10.0.3.147 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.147:22 失败: dial tcp 10.0.3.147:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.148 () 开始压测... - [MAC] 10.0.3.148 -> fe:49:6a:ab:e1:79 - -════════ [10.0.3.148] 压力测试开始 ════════ - 工具: stress-ng(stress-ng, version 0.15.06 (gcc 12.2.0, x86_64 Linux 6.18.18.c877-trim)) fio(fio-3.33) - 指标: cpu memory disk full dmesg - [脚本] 拼装完成,10334 字节 - [报告] reports/report-10.0.3.148.html - -════════ [10.0.3.148] 压力测试完成 ════════ -══ [10.0.3.148] 压力测试报告 ══ - 耗时: 2m23.341s - - ✓ cpu pass - 跑分: 1622.1 ops/s - 最高温度: 53.0°C - 平均CPU利用率: 98.4% - stress-ng: info: [3783625] Working directory / is not read/writeable, some I/O tests may fail - stress-ng: info: [3783625] setting to a 30 second run per stressor - stress-ng: info: [3783625] dispatching hogs: 4 cpu - stress-ng: metrc: [3783625] stressor bogo ops real time usr time sys time bogo ops/s bogo ops/s - stress-ng: metrc: [3783625] (secs) (secs) (secs) (real time) (usr+sys time) - stress-ng: metrc: [3783625] cpu 48668 30.00 113.93 0.32 1622.06 426.00 - stress-ng: info: [3783625] successful run completed in 30.02s - ✓ memory pass - 跑分: 44033.6 ops/s - 最高温度: 53.0°C - 峰值内存使用率: 52.5% - stress-ng: info: [3783877] Working directory / is not read/writeable, some I/O tests may fail - stress-ng: info: [3783877] setting to a 30 second run per stressor - stress-ng: info: [3783877] dispatching hogs: 4 vm - stress-ng: metrc: [3783877] stressor bogo ops real time usr time sys time bogo ops/s bogo ops/s - stress-ng: metrc: [3783877] (secs) (secs) (secs) (real time) (usr+sys time) - stress-ng: metrc: [3783877] vm 1321192 30.00 96.84 5.67 44033.61 12888.25 - stress-ng: info: [3783877] successful run completed in 30.01s - ✓ disk pass - 跑分: 391.0 MB/s - 读IOPS: 67.0 - 写IOPS: 29.0 - 总IOPS: 96.0 - 读吞吐: 271.0MB/s - 写吞吐: 120.0MB/s - 总吞吐: 391.0MB/s - autocheck: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=psync, iodepth=32 - fio-3.33 - Starting 1 process - autocheck: Laying out IO file (1 file / 1024MiB) - note: both iodepth >= 1 and synchronous I/O engine are selected, queue depth will be capped at 1 - autocheck: (groupid=0, jobs=1): err= 0: pid=3784176: Fri Aug 14 01:21:03 2026 - read: IOPS=67, BW=271KiB/s (277kB/s)(8328KiB/30763msec) - clat (usec): min=256, max=919497, avg=14116.10, stdev=41899.33 - lat (usec): min=257, max=919500, avg=14118.33, stdev=41899.33 - clat percentiles (msec): - | 1.00th=[ 4], 5.00th=[ 5], 10.00th=[ 6], 20.00th=[ 7], - | 30.00th=[ 8], 40.00th=[ 10], 50.00th=[ 11], 60.00th=[ 12], - | 70.00th=[ 13], 80.00th=[ 14], 90.00th=[ 15], 95.00th=[ 18], - | 99.00th=[ 65], 99.50th=[ 74], 99.90th=[ 869], 99.95th=[ 894], - | 99.99th=[ 919] - bw ( KiB/s): min= 84, max= 396, per=100.00%, avg=277.27, stdev=87.49, samples=30 - iops : min= 21, max= 99, avg=69.30, stdev=21.85, samples=30 - write: IOPS=29, BW=120KiB/s (123kB/s)(3684KiB/30763msec); 0 zone resets - clat (usec): min=135, max=285109, avg=1405.37, stdev=9787.22 - lat (usec): min=136, max=285113, avg=1408.31, stdev=9787.24 - clat percentiles (usec): - | 1.00th=[ 212], 5.00th=[ 289], 10.00th=[ 338], 20.00th=[ 400], - | 30.00th=[ 469], 40.00th=[ 537], 50.00th=[ 627], 60.00th=[ 758], - | 70.00th=[ 881], 80.00th=[ 1037], 90.00th=[ 1270], 95.00th=[ 1549], - | 99.00th=[ 17433], 99.50th=[ 22414], 99.90th=[283116], 99.95th=[283116], - | 99.99th=[283116] - bw ( KiB/s): min= 32, max= 192, per=100.00%, avg=122.73, stdev=43.53, samples=30 - iops : min= 8, max= 48, avg=30.63, stdev=10.85, samples=30 - lat (usec) : 250=0.87%, 500=9.59%, 750=7.86%, 1000=5.76% - lat (msec) : 2=5.56%, 4=1.00%, 10=32.17%, 20=33.70%, 50=0.87% - lat (msec) : 100=2.33%, 250=0.07%, 500=0.07%, 750=0.07%, 1000=0.10% - cpu : usr=0.56%, sys=1.94%, ctx=3031, majf=0, minf=12 - IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% - submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% - complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% - issued rwts: total=2082,921,0,0 short=0,0,0,0 dropped=0,0,0,0 - latency : target=0, window=0, percentile=100.00%, depth=32 - Run status group 0 (all jobs): - READ: bw=271KiB/s (277kB/s), 271KiB/s-271KiB/s (277kB/s-277kB/s), io=8328KiB (8528kB), run=30763-30763msec - WRITE: bw=120KiB/s (123kB/s), 120KiB/s-120KiB/s (123kB/s-123kB/s), io=3684KiB (3772kB), run=30763-30763msec - Disk stats (read/write): - sda: ios=2085/984, merge=0/88, ticks=28333/21834, in_queue=54712, util=93.86% - ✓ full pass - 跑分: 33777.8 ops/s - 最高温度: 54.0°C - 平均CPU利用率: 92.1% - 峰值内存使用率: 51.9% - stress-ng: info: [3784311] Working directory / is not read/writeable, some I/O tests may fail - stress-ng: info: [3784311] setting to a 30 second run per stressor - stress-ng: info: [3784311] dispatching hogs: 4 cpu, 4 vm, 2 iomix - stress-ng: metrc: [3784311] stressor bogo ops real time usr time sys time bogo ops/s bogo ops/s - stress-ng: metrc: [3784311] (secs) (secs) (secs) (real time) (usr+sys time) - stress-ng: metrc: [3784311] cpu 24082 30.01 57.65 0.34 802.40 415.28 - stress-ng: metrc: [3784311] vm 1005657 30.64 44.02 4.26 32825.38 20831.04 - stress-ng: metrc: [3784311] iomix 4543 30.28 2.18 3.87 150.03 750.28 - stress-ng: info: [3784311] successful run completed in 32.51s - ✓ dmesg pass - 无硬件相关内核报错 - - 共 5 项: 5 通过, 0 失败, 0 跳过, 0 错误 - - [测试] 10.0.3.160 () 开始压测... - [测试] 10.0.3.160 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.160:22 失败: dial tcp 10.0.3.160:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.152 () 开始压测... - [测试] 10.0.3.152 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.152:22 失败: dial tcp 10.0.3.152:22: connectex: No connection could be made because the target machine actively refused it. - [测试] 10.0.3.162 () 开始压测... - [测试] 10.0.3.162 SSH 连接失败,跳过报告输出: SSH 连接 10.0.3.162:22 失败: dial tcp 10.0.3.162:22: connectex: No connection could be made because the target machine actively refused it. - [状态] 10.0.3.4 ✗ → fail - [状态] 10.0.3.104 ✗ → fail - [状态] 10.0.3.147 ✗ → fail - [状态] 10.0.3.152 ✗ → fail - [状态] 10.0.3.236 ✗ → fail - [状态] 10.0.3.1 ✗ → fail - [状态] 10.0.3.123 ✗ → fail - [状态] 10.0.3.119 ✗ → fail - [状态] 10.0.3.148 (fe:49:6a:ab:e1:79) ✓ → pass - [状态] 10.0.3.160 ✗ → fail - [状态] 10.0.3.162 ✗ → fail - [状态] 10.0.3.171 ✗ → fail - [状态] 10.0.3.175 ✗ → fail diff --git a/fnhelp/README.md b/fnhelp/README.md deleted file mode 100644 index ec23ba9..0000000 --- a/fnhelp/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# 飞牛 fnOS 帮助中心 - -本文档是从 [飞牛 NAS 帮助中心](https://help.fnnas.com/) 爬取并整理的离线版本。 - -## 目录结构 - -### 1. 从这里开始 -- [如何安装和初始化飞牛 fnOS?](./start/install-os.md) -- [如何安装 App 并连接到飞牛 NAS?](./start/install-app.md) -- [如何安装飞牛 TV 并连接到影视服务器?](./start/install-tv.md) - -### 2. 存储管理 -- [如何创建存储空间?](./volume/create.md) -- [硬盘会自动休眠吗?](./volume/hibernate.md) -- [如何使用 SSD 缓存加速?](./volume/ssd-cache.md) - -### 3. 飞牛影视 -- [如何安装飞牛影视和创建媒体库?](./media/media-install.md) -- [如何为家人朋友开通影视账号?](./media/create-users.md) -- [Apple TV 上如何通过 Infuse 使用飞牛影视?](./media/atv-infuse.md) - -### 4. 相册 -- [如何备份手机照片?](./photo/photo-backup.md) -- [如何共享相册给设备内其他用户?](./photo/photo-share.md) -- [AI 相册功能说明](./photo/photo-ai.md) - -### 5. 远程访问 -- [如何远程访问到飞牛 NAS?](./access/how-access.md) -- [如何管理 FN Connect 权益?](./access/how-to-set.md) - -### 6. 系统设置 -- [如何创建新用户?](./settings/create-users.md) -- [如何使用无线网卡?](./settings/wifi.md) -- [如何修改飞牛系统的端口?](./settings/port-customization.md) - -### 7. 文件管理 -- [如何将我的文件共享给其他用户?](./file/share.md) -- [如何创建文件的外部分享链接?](./file/create-share-link.md) -- [如何共享团队文件?](./file/team.md) - -### 8. 文件共享协议 -- [Windows 通过 WebDAV 挂载飞牛注意事项](./file-service/webdav.md) -- [Windows 通过 NFS 挂载飞牛注意事项](./file-service/nfs.md) -- [SMB 3 多通道生效条件](./file-service/smb3-multichannel.md) - -### 9. 虚拟机 -- [如何安装和使用虚拟机?](./virtual-machine/install.md) -- [如何使用虚拟机硬件直通?](./virtual-machine/passthrough.md) - -### 10. 飞牛同步 -- [飞牛同步功能介绍](./sync/how-to-use-fnsync.md) -- [飞牛同步常见使用问题说明](./sync/fnsync-faq.md) - -### 11. 备份 -- [加密备份功能介绍](./backup/encrypted.md) - -### 12. iSCSI -- [iSCSI 名词介绍](./iscsi/iscsi-definition.md) -- [iSCSI 基础功能介绍](./iscsi/iscsi-create.md) -- [iSCSI 用户组配置说明](./iscsi/iscsi-group.md) - -### 13. AI & OpenClaw -- [飞牛 OpenClaw 配置微信 ClawBot 教程](./ai/openclaw-weixin.md) -- [飞牛 OpenClaw 配置钉钉机器人教程](./ai/openclaw-dingtalk.md) -- [飞牛 OpenClaw 配置飞书机器人教程](./ai/openclaw-feishu.md) - -### 14. 安全性 -- [安全披露](./safety/vulnerability-report.md) -- [安全漏洞报告奖励计划](./safety/safe-report.md) - -### 15. 更多 -- [联系我们](./contact/contact-us.md) -- [本地账号与飞牛账号的区别?](./contact/account-diff.md) -- [管理员和普通用户权限的区别?](./contact/user-role.md) - ---- - -> 📅 爬取日期:2026-04-08 -> 🌐 来源:https://help.fnnas.com/ diff --git a/fnhelp/access/how-access.md b/fnhelp/access/how-access.md deleted file mode 100644 index c6f29ed..0000000 --- a/fnhelp/access/how-access.md +++ /dev/null @@ -1,43 +0,0 @@ -# 如何远程访问到飞牛 NAS? - -飞牛 fnOS 有提供公网 IP 直连、FN Connect 远程访问服务、DDNS 域名转发、P2P 等多种远程访问方式。 - -## 方法1:公网 IP 直连 - -你可以与运营商申请公网 IP,并且在路由器中配置端口转发。打开网页浏览器或者在飞牛 App 上直接输入公网 IPv4 地址或 IPv6 地址以及端口号即可访问。 - -> **注意**: -> - 飞牛 fnOS 的 HTTP 访问端口为 8000,HTTPS 访问端口为 8001; -> - 若有配置端口转发,必须要在访问地址后增加端口号才能正确访问到飞牛 NAS; -> - 公网 IP 直连的网络速度无折损,但公网 IP 直连无安全证书,HTTP 访问明文传输,请在安全的网络环境下访问。 - -## 方法2:配置 DDNS 域名和证书 - -DDNS 服务将自动定时更新你的飞牛 NAS 的出口 IP,并更新 IP 映射域名,通过域名即可轻松访问到飞牛 NAS。在此基础上,配置对应域名的 SSL 证书,可通过 HTTPS 安全访问到你的飞牛 NAS。 - -> **注意**: -> - 购买并配置域名及证书; -> - 飞牛支持以下服务商的 DDNS 服务:腾讯云、阿里云、华为云、花生壳、DuckDNS; -> - 推荐配置 DDNS 域名及证书,速度无损且安全。 - -## 方法3:FN Connect 远程访问服务 - -FN Connect 是由飞牛自研的远程访问服务,可以为你提供安全、高效远程访问到飞牛 NAS 的服务。使用本服务,你需创建唯一的 FN ID 以标识你的飞牛 NAS 设备。 - -> **注意**: -> - 使用 FN Connect 服务,你将被要求注册或登录飞牛账号; -> - FN Connect 服务将为你的飞牛 NAS 创建 FN ID 对应的**唯一子域名的 SSL 证书**,可安全地访问到飞牛 NAS; -> - FN Connect 将根据当下你所处的网络环境,判断选择最优的访问方式自动连接; -> - FN Connect 中继转发服务会产生流量成本,因此会有**限速**。 - -## 方法4:客户端 P2P 连接 - -使用飞牛 App,在使用 FN Connect 服务的基础下,如果网络的 IPv6 可以通,则会自动切换到 P2P 方式直连到你的飞牛 NAS。 - -> **注意**: -> - P2P 连接状态下,可能因为网络不稳定而回退至**中继转发**。 -> - P2P 连接,没有经过飞牛服务器,而是飞牛 NAS 设备与手机客户端直接加密通信,因此**安全无限速**。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/access/how-to-set.md b/fnhelp/access/how-to-set.md deleted file mode 100644 index 782e0ca..0000000 --- a/fnhelp/access/how-to-set.md +++ /dev/null @@ -1,49 +0,0 @@ -# 如何管理 FN Connect 权益? - -## 1. 绑定操作 - -### 1.1 购买后直接绑定 - -- 单台 NAS:自动绑定 -- 多台 NAS:购买后弹出绑定弹窗选择设备 -- 首次绑定需要3分钟线路匹配时间 - -### 1.2 购买后延迟绑定 - -- 通过"我的账号-我的设备"进入设备管理 -- 支持选择多个未生效权益进行绑定 -- 一台NAS只能绑定一个已生效权益 - -### 1.3 调整权益激活顺序 - -- 可拖动调整未生效权益的顺序 -- 已生效权益固定为第一位 - -## 2. 解绑操作 - -- 未生效权益解绑后仍为未生效状态 -- 已生效权益解绑后会持续计算使用时长 -- 解绑后会自动激活下一个未生效权益 - -## 3. 换绑操作 - -- 支持两台NAS之间已生效权益的对调 -- 不影响未生效权益的绑定情况 - -## 4. 其他重要说明 - -- 权益归属于购买时的飞牛账号 -- 设备重装或解绑不会丢失权益 -- 3天内支持无责任退款 -- 带宽问题可通过订单咨询提交工单 - -## 关键注意事项 - -- 权益绑定后需要3分钟线路匹配时间 -- 一台NAS只能有一个已生效权益 -- 解绑已生效权益需谨慎操作 -- 权益与账号绑定,不随设备变化而丢失 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/ai/openclaw-dingtalk.md b/fnhelp/ai/openclaw-dingtalk.md deleted file mode 100644 index 31666f4..0000000 --- a/fnhelp/ai/openclaw-dingtalk.md +++ /dev/null @@ -1,27 +0,0 @@ -# 飞牛 OpenClaw 配置钉钉机器人教程 - -## 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - - 官方合作推荐:阿里云百炼,免费体验 7000 万 Token。 - -## 2. 钉钉机器人配置 - -### 2.1 打开钉钉开发者平台并创建应用 - -1. 登录[开发者后台](https://open-dev.dingtalk.com/)。 -2. 在**应用开发**下,点击**立即创建**,可一键创建 OpenClaw 机器人。 -3. 在创建 OpenClaw 界面,填写机器人基本信息。 -4. OpenClaw 创建成功后,会自动展示应用的 **Client ID** 和 **Client Secret**,请保存好用于后续使用。 - -### 2.2 配置钉钉机器人消息渠道 - -1. 回到飞牛 OpenClaw。 -2. 打开"消息渠道" -> "添加渠道"。 -3. 选择"钉钉",输入 Client ID 和 Client Secret,并保存。 -4. 重启 OpenClaw,回到钉钉发送给机器人消息,有回复,即完成钉钉机器人配置。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/ai/openclaw-feishu.md b/fnhelp/ai/openclaw-feishu.md deleted file mode 100644 index 12b9316..0000000 --- a/fnhelp/ai/openclaw-feishu.md +++ /dev/null @@ -1,64 +0,0 @@ -# 飞牛 OpenClaw 配置飞书机器人教程 - -## 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - -## 2. 飞书机器人配置 - -### 2.1 打开飞书开放平台并创建应用 - -1. 打开飞书开放平台:https://open.feishu.cn/app -2. 登录你的飞书账号 -3. 点击"创建企业自建应用" - -### 2.2 获取 App ID 与 App Secret - -1. 创建完成后,进入应用详情页 -2. 记录 App ID 和 App Secret - -> ⚠️ **重要**:App Secret 务必保密,不要截图外传,不要发到群里。 - -### 2.3 权限配置 - -点击左侧"权限管理",点击"批量导入权限",粘贴相关权限配置。 - -### 2.4 启用机器人 - -1. 点击左侧"应用能力" -2. 找到"机器人"卡片,点击"启用" -3. 设置机器人名称 -4. 点击"保存" - -### 2.5 发布版本 - -1. 点击左侧"版本管理与发布" -2. 点击"创建版本" -3. 填写版本信息 -4. 点击"保存" -5. 点击"申请发布" - -> 💡 **提示**:审批通过后,应用状态会变为"已发布"。这时候才能进行下一步。 - -### 2.6 在飞牛 OpenClaw 中配置飞书机器人 - -1. 在消息渠道中配置"添加渠道",选择飞书,输入 App ID 与 App Secret,并保存 -2. 重启 OpenClaw 网关 - -### 2.7 在飞书平台开启事件订阅 - -1. 回到飞书开放平台 -2. 点击左侧"事件与回调" -3. 在"事件订阅方式"中,选择"长连接"并保存 -4. 在"订阅事件"区域,点击"添加事件",搜索 `im.message.receive_v1` - -### 2.8 完成配对并验证 - -1. 在飞书中找到你的机器人,进入私聊,发送任何消息,获得配对码 -2. 在 OpenClaw 中完成配对 -3. 只要机器人正常回复即可 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/ai/openclaw-weixin.md b/fnhelp/ai/openclaw-weixin.md deleted file mode 100644 index d3eb723..0000000 --- a/fnhelp/ai/openclaw-weixin.md +++ /dev/null @@ -1,30 +0,0 @@ -# 飞牛 OpenClaw 配置微信 ClawBot 教程 - -## 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - - 官方合作推荐:阿里云百炼,免费体验 7000 万 Token。 - -## 2. 微信 ClawBot 配置 - -### 2.1 确认微信有 ClawBot 功能 - -微信正在逐步放开 ClawBot 功能,需要先升级微信到最新版本后,再确认当前的微信是否已有 ClawBot 功能。 - -1. 打开微信设置。 -2. 打开插件。 -3. 若看到微信 ClawBot 功能,则确定有 ClawBot 功能。 - -### 2.2 微信扫码完成绑定 - -1. 回到飞牛 OpenClaw。 -2. 打开"消息渠道" -> "添加渠道"。 -3. 选择"微信" -> "生成二维码"。 -4. 使用手机微信扫描二维码绑定。 -5. 手机上确定连接。 -6. 重启 OpenClaw,用手机发送消息,有回复,即完成微信 ClawBot 配置。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/backup/encrypted.md b/fnhelp/backup/encrypted.md deleted file mode 100644 index d54458d..0000000 --- a/fnhelp/backup/encrypted.md +++ /dev/null @@ -1,52 +0,0 @@ -# 加密备份功能介绍 - -## 1. 备份飞牛时开启加密 - -加密备份功能允许在将数据从飞牛NAS备份到百度网盘或阿里云盘时,对文件进行轻量化加密。 - -**操作步骤:** - -1. 进入"备份飞牛",选择目标网盘(百度网盘/阿里云盘)。 -2. 在第四步"备份规则"页面,启用"数据加密"并设置密码。 -3. 确认创建任务后即可开始加密备份。 - -**注意事项:** - -- 密码最长32位,区分英文大小写,支持特殊符号:`! @ # % & * ~`。 -- 加密后本地文件保持不加密状态。 -- 加密后的文件名会被混淆,扩展名变更为`.fot`。 -- 加密任务创建后无法取消数据加密设置,请妥善保管密码。 - -## 2. 备份网盘时开启解密 - -当从网盘恢复数据到飞牛NAS时,需启用解密功能。 - -**操作步骤:** - -1. 进入"备份远程存储",选择来源网盘。 -2. 在第四步"备份规则"页面,启用"数据解密"并输入密码。 -3. 确认创建任务后即可开始解密。 - -## 3. 常见问题 - -**Q1:加密后文件名会变吗?** - -A:会。加密后文件名会被混淆,后缀统一变为`.fot`。 - -**Q2:什么情况下备份至百度网盘的加密任务失败?** - -A:因百度网盘限制,需生成临时加密文件存放在本地后再上传。 - -**Q3:什么情况会解密失败?** - -A:常见原因: -1. 密码错误(注意英文大小写和特殊符号)。 -2. 配置解密任务后频繁修改网盘中加密文件夹。 - -**Q4:忘记密码怎么办?** - -A:为保障数据安全,密码以密文形式存储在本地,不会上报至飞牛服务器,因此无法找回。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/contact/account-diff.md b/fnhelp/contact/account-diff.md deleted file mode 100644 index 852edb4..0000000 --- a/fnhelp/contact/account-diff.md +++ /dev/null @@ -1,25 +0,0 @@ -# 本地账号与飞牛账号的区别? - -## 本地账号 - -- **用途**:用于访问本地网络中的飞牛 NAS 设备的用户验证。 -- **访问场景**: - - 飞牛 fnOS 登录页面; - - 飞牛 App 登录页面。 -- **如何创建**: - - 在系统安装及初始化时,创建管理员账号; - - 在 **系统设置** > **用户管理**,可创建普通用户账号。 -- **忘记密码**: - - 管理员:如果有其他管理员,可联系其他管理员进行重置密码。若无,请重置飞牛 fnOS。 - - 普通用户:请联系管理员进行重置密码。 - -## 飞牛账号 - -- **用途**:在使用飞牛云服务,比如 **FN Connect 远程服务** 时,飞牛 NAS 需要绑定有效的 **飞牛账号**。 -- **访问场景**:[飞牛账号](https://account.fnnas.com/login) 登录页面。 -- **如何创建**:填写手机号码,短信验证成功后,填写基础信息完成创建。 -- **忘记密码**:点击 [忘记密码](https://account.fnnas.com/forget),填写手机号,验证成功后可重置密码。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/contact/contact-us.md b/fnhelp/contact/contact-us.md deleted file mode 100644 index 96c7a3b..0000000 --- a/fnhelp/contact/contact-us.md +++ /dev/null @@ -1,26 +0,0 @@ -# 联系我们 - -## 官网链接 - -- 飞牛官网:https://www.fnnas.com - -## 公众号 - -- 关注飞牛官方公众号:"飞牛fnOS",了解最新消息。 - -## 商务合作 - -- 商务合作,请添加飞牛商务微信:fn_business - -## 功能问题/意见反馈 - -- 请填写[问题/意见反馈表单](https://trim-nas.feishu.cn/share/base/form/shrcn2iVepeqaASZzMF0iMNAC5e) -- 可加入飞牛公测反馈群,与飞牛团队面对面 - -## 应用生态 - -- 请求上架飞牛应用中心,请填写[申请表](https://trim-nas.feishu.cn/share/base/form/shrcn5m4MsyeqvEh6c4tm97pQKb),提交后,请静候。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/contact/user-role.md b/fnhelp/contact/user-role.md deleted file mode 100644 index e289858..0000000 --- a/fnhelp/contact/user-role.md +++ /dev/null @@ -1,22 +0,0 @@ -# 管理员和普通用户权限的区别? - -在 fnOS 上,用户分为 **管理员** 和 **普通用户** 两种角色。 - -## 超级管理员 - -**超级管理员** 特指在系统初始化时创建的第一个管理员账号,其用户 ID 为 1000。相较于其他管理员,超级管理员的特殊性仅体现在以下两点: - -- 创建后不能被删除,只能被停用。 -- 默认开启 SSH 权限(可在 **设置 > 用户管理 > 用户详情 > 更多** 中关闭)。 - -## 关键信息总结 - -| 角色类型 | 关键权限/特点 | -|----------|---------------| -| **管理员** | 拥有系统管理权限,可进行各项设置操作 | -| **普通用户** | 权限受限,仅能使用被授权的功能 | -| **超级管理员** | 初始化创建,UID 1000,不可删除,默认开启 SSH | - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file-service/nfs.md b/fnhelp/file-service/nfs.md deleted file mode 100644 index df2b00f..0000000 --- a/fnhelp/file-service/nfs.md +++ /dev/null @@ -1,28 +0,0 @@ -# Windows 通过 NFS 挂载飞牛注意事项 - -## 1. 开启 NFS 服务 - -- **目的:** 在 Windows 系统中启用 NFS 客户端功能,以便能够通过 NFS 协议挂载飞牛 NAS。 - -- **步骤:** - 1. 打开"控制面板" > "程序" > "启用或关闭 Windows 功能"。 - 2. 找到"NFS 服务",勾选所有子项(如"NFS 客户端"等)。 - 3. 点击"确定"保存设置,系统会自动安装所需组件。 - -## 2. 文件名乱码修正 - -- **问题描述:** 在挂载后,文件名可能显示为乱码,原因是 Windows 和飞牛 NAS 使用的字符编码不匹配。 - -- **解决方案:** - 1. 使用快捷键 `Win + R` 打开"运行"窗口,输入 `intl.cpl` 并回车。 - 2. 在"区域"设置中,切换到"管理"选项卡,点击"更改系统区域设置"。 - 3. 勾选"Beta 版:使用 Unicode UTF-8 提供全球语言支持"。 - 4. 点击"确定"保存设置,并重启计算机使配置生效。 - -## 其他注意事项 - -- 除了通过 Windows 自带的文件资源管理器添加网络位置外,用户还可以使用第三方工具(如 RaiDrive、WinSCP、FileZilla)来挂载 NFS 共享。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file-service/smb3-multichannel.md b/fnhelp/file-service/smb3-multichannel.md deleted file mode 100644 index 3f836e2..0000000 --- a/fnhelp/file-service/smb3-multichannel.md +++ /dev/null @@ -1,15 +0,0 @@ -# SMB 3 多通道生效条件 - -多通道是 SMB 3 的一项功能,可以让多个有线网络分担流量来提高传输速度和增强可靠性。 - -若你已在飞牛 **系统设置** > **文件共享协议** > **SMB** > **高级设置** 中开启了 **SMB 3 多通道**,但速度上未有显著提升,请参考此文档检查以下条件是否满足: - -1. NAS 和客户端都具有多个网口,且 NAS 上实际连接的网口数量是客户端网口数量的整数倍; -2. NAS 与客户端网口可以互相通信(在同一子网或正确路由下); -3. NAS 与客户端所有网口都具有相同的速率(否则,当有不同速率的网口时,系统可能会优先选择最快的网口); -4. 客户端支持 SMB 3 多通道,包括 Windows 8、Windows Server 2012、macOS 10.13 (High Sierra) 及以上版本; -5. 硬盘及 CPU 性能良好,非性能瓶颈。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file-service/webdav.md b/fnhelp/file-service/webdav.md deleted file mode 100644 index 5bfa409..0000000 --- a/fnhelp/file-service/webdav.md +++ /dev/null @@ -1,29 +0,0 @@ -# Windows 通过 WebDAV 挂载飞牛注意事项 - -## 一、开启 HTTP 支持 - -Windows 默认不支持通过 HTTP 协议连接 WebDAV 服务,需要通过修改注册表来开启。具体步骤如下: - -1. 使用快捷键 Win + R 打开运行窗口,输入 `regedit` 打开注册表编辑器。 -2. 在地址栏中粘贴路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters`,然后按回车键。 -3. 找到 `BasicAuthLevel` 项,双击打开修改窗口,将数值数据改为 `2`,然后点击确定。 -4. 再次使用 Win + R 打开运行窗口,输入 `services.msc` 打开服务窗口。 -5. 找到 `WebClient` 服务,右键选择"重新启动"。 - -## 二、解决文件大小超出限制的问题 - -Windows WebDAV 默认传输文件大小限制为 50MB,可以通过修改注册表来调整这一限制。具体操作如下: - -1. 使用 Win + R 打开运行窗口,输入 `regedit` 打开注册表编辑器。 -2. 在地址栏中粘贴路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters` -3. 找到 `FileSizeLimitInBytes` 项,双击打开修改窗口,将默认值 `2faf080`(50MB)修改为 `ffffffff`(4GB) -4. 再次使用 Win + R 打开运行窗口,输入 `services.msc` 打开服务窗口。 -5. 找到 `WebClient` 服务,右键选择"重新启动"。 - -## 其他说明 - -- 文章还提到了在 Windows 上挂载 WebDAV 的其他方式,例如使用第三方文件管理客户端(如 RaiDrive、WinSCP、FileZilla)。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file/create-share-link.md b/fnhelp/file/create-share-link.md deleted file mode 100644 index ca474fe..0000000 --- a/fnhelp/file/create-share-link.md +++ /dev/null @@ -1,40 +0,0 @@ -# 如何创建文件的外部分享链接? - -将 fnOS 更新到 V0.8.26 版本之后,你可以在 **文件管理** 中创建文件的 **外部分享链接**,你的家人朋友可以通过此链接访问并且下载你分享的文件。 - -## 如何设置外部分享链接? - -打开 **文件管理**,在左下角可打开 **文件管理设置** - **外链分享设置**。 - -你可以在 **外链分享设置** 中,设置以下功能: - -- **外链分享权限**:可以选择 **所有用户** 或 **仅管理员** 都能够使用 **外链分享** -- **分享链接设置**: - - **默认链接**:若你有开启 FN Connect 远程访问服务,则每次分享时会生成 FN Connect 专属的文件外链分享链接。 - - **自定义链接**:可设置生成的外部分享链接使用 HTTPS 或 HTTP 协议。 - -## 如何创建外部分享链接? - -在 **文件管理**,可选中文件,点击右键或更多,创建外部分享链接。 - -创建外部分享链接时,你可以设置: - -- **到期时间**:若不勾选,则为 **永久**。 -- **密码**:若不勾选,则为 **无密码**,若勾选,请输入至少4位的密码。 -- **可访问次数**:默认不勾选,若不勾选,则为 **无限制次数** - -点击确定,即可创建外部分享链接。同一个文件可分别创建多个不同的分享链接。 - -## 如何管理外部分享链接? - -你可以在 **文件管理** - **外链分享** 中看见并管理的所有你创建的文件外部分享链接。 - -文件外部分享链接,有以下 3 个状态: - -1. **有效**:未达到期时间、可访问次数不为0、当前用户有使用功能的权限。 -2. **失效**:时间已经到了到期时间 或者 可访问次数 = 0,则此链接失效。 -3. **无效**:用户无外链分享功能的使用权限等异常情况会导致链接无效。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file/share.md b/fnhelp/file/share.md deleted file mode 100644 index ca416e6..0000000 --- a/fnhelp/file/share.md +++ /dev/null @@ -1,34 +0,0 @@ -# 如何将我的文件共享给其他用户? - -在 fnOS 上,你可以在多个用户之间共享位于 NAS 存储空间上的文件。文件所在的位置不同,共享的方法也有所差别: - -1. 文件位于 **我的文件** 下,即存放于某个用户的个人目录中:目录所有者可通过 **共享给设备的用户** 功能,允许其他用户从 **他人共享** 中访问其个人目录下的文件。 - -2. 文件位于 **团队文件夹** 中,即存放于存储空间下的 @team 目录中,不归属于任何用户:管理员可对团队文件夹及其子文件夹或文件设置用户/用户组的访问权限。 - -## 如何将我的文件共享给他人? - -1. 选择 **我的文件** 下的任一文件夹,右键,点击 **共享给设备内的用户**,打开共享设置弹窗。 -2. 在页面左侧选择要共享给哪些用户或用户组,可搜索名称或备注。 -3. 在右侧选择被共享者的访问权限,可选择只读、读写或禁止访问。选择后,点击确认。 -4. 设置共享名称,此名称将展示给被共享者。默认为文件夹名称。确认后,点击开启共享。 - -## 如何修改共享权限? - -1. 打开 **文件管理 > 我的共享**,找到要修改的共享文件夹,右键点击共享设置。 -2. 修改权限,或新增用户,点击确定。 -3. 选择是否要将此次修改应用到已存在的子文件夹和文件上。 - -## 被共享者如何访问他人共享的文件? - -1. 打开 **文件管理 > 他人共享**,双击共享者。 -2. 即可查看到被共享的文件夹。 - -## 如何关闭共享? - -1. 打开 **文件管理 > 我的共享**,找到要修改的共享文件夹,右键点击共享设置。点击关闭共享。 -2. 关闭共享的本质是移除已为其他用户添加的权限。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/file/team.md b/fnhelp/file/team.md deleted file mode 100644 index e170e13..0000000 --- a/fnhelp/file/team.md +++ /dev/null @@ -1,35 +0,0 @@ -# 如何共享团队文件? - -本文详细介绍了在飞牛 fnOS 系统中如何共享团队文件,包括团队文件夹的创建、权限设置、成员管理以及临时共享子文件的方法。 - -## 1. 团队文件夹的创建 - -- 登录管理员账号,进入"我的文件 > 团队文件",点击"新建团队文件夹"。 -- 填写文件夹名称、选择存储空间,并设置容量上限、文件共享协议权限及回收站管理权限。 -- 设置成员及权限(支持按用户、用户组或应用设置只读、读写或禁止访问)。 - -## 2. 修改团队文件夹的成员权限 - -- 在"团队文件"中找到目标文件夹,右键选择"成员权限"进行修改。 -- 支持将权限变更应用到现有子文件夹和文件。 - -## 3. 收紧子文件权限 - -- 针对特定子文件夹或文件,可通过"详细信息 > 权限"设置独立于父级的权限。 -- 适用于限制用户访问部分内容的情况。 - -## 4. 临时共享子文件给非团队成员 - -- 对子文件设置权限后,复制其原始路径发送给被共享者。 -- 被共享者可通过粘贴路径直接访问,但无权限浏览上层目录。 - -## 关键注意事项 - -- **权限继承规则:** - - 用户组权限由组内成员继承,多组权限取并集。 - - 用户单独设置的权限优先级高于用户组权限。 -- **功能依赖:** 团队文件功能需 fnOS V0.9.35 或更高版本支持。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/fnOS完整帮助文档.md b/fnhelp/fnOS完整帮助文档.md deleted file mode 100644 index ea1e251..0000000 --- a/fnhelp/fnOS完整帮助文档.md +++ /dev/null @@ -1,1874 +0,0 @@ -# 飞牛 fnOS 帮助中心完整文档 - -> 来源:https://help.fnnas.com/ -> 整理日期:2026-04-08 - ---- - -# 目录 - -1. [从这里开始](./start) - - [如何安装和初始化飞牛 fnOS?](./start#如何安装和初始化飞牛-fnOS) - - [如何安装 App 并连接到飞牛 NAS?](./start#如何安装-App-并连接到飞牛-NAS) - - [如何安装飞牛 TV 并连接到影视服务器?](./start#如何安装飞牛-TV-并连接到影视服务器) - -2. [存储管理](./volume) - - [如何创建存储空间?](./volume#如何创建存储空间) - - [硬盘会自动休眠吗?](./volume#硬盘会自动休眠吗) - - [如何使用 SSD 缓存加速?](./volume#如何使用-SSD-缓存加速) - -3. [飞牛影视](./media) - - [如何安装飞牛影视和创建媒体库?](./media#如何安装飞牛影视和创建媒体库) - - [如何为家人朋友开通影视账号?](./media#如何为家人朋友开通影视账号) - - [Apple TV 上如何通过 Infuse 使用飞牛影视?](./media#apple-tv-上如何通过-infuse-使用飞牛影视) - -4. [相册](./photo) - - [如何备份手机照片?](./photo#如何备份手机照片) - - [如何共享相册给设备内其他用户?](./photo#如何共享相册给设备内其他用户) - - [AI 相册功能说明](./photo#ai-相册功能说明) - -5. [远程访问](./access) - - [如何远程访问到飞牛 NAS?](./access#如何远程访问到飞牛-NAS) - - [如何管理 FN Connect 权益?](./access#如何管理-FN-Connect-权益) - -6. [系统设置](./settings) - - [如何创建新用户?](./settings#如何创建新用户) - - [如何使用无线网卡?](./settings#如何使用无线网卡) - - [如何修改飞牛系统的端口?](./settings#如何修改飞牛系统的端口) - -7. [文件管理](./file) - - [如何将我的文件共享给其他用户?](./file#如何将我的文件共享给其他用户) - - [如何创建文件的外部分享链接?](./file#如何创建文件的外部分享链接) - - [如何共享团队文件?](./file#如何共享团队文件) - -8. [文件共享协议](./file-service) - - [Windows 通过 WebDAV 挂载飞牛注意事项](./file-service#windows-通过-webdav-挂载飞牛注意事项) - - [Windows 通过 NFS 挂载飞牛注意事项](./file-service#windows-通过-nfs-挂载飞牛注意事项) - - [SMB 3 多通道生效条件](./file-service#smb-3-多通道生效条件) - -9. [虚拟机](./virtual-machine) - - [如何安装和使用虚拟机?](./virtual-machine#如何安装和使用虚拟机) - - [如何使用虚拟机硬件直通?](./virtual-machine#如何使用虚拟机硬件直通) - -10. [飞牛同步](./sync) - - [飞牛同步功能介绍](./sync#飞牛同步功能介绍) - - [飞牛同步 - 常见使用问题说明](./sync#飞牛同步---常见使用问题说明) - -11. [备份](./backup) - - [加密备份功能介绍](./backup#加密备份功能介绍) - -12. [iSCSI](./iscsi) - - [iSCSI 名词介绍](./iscsi#iscsi-名词介绍) - - [iSCSI 基础功能介绍](./iscsi#iscsi-基础功能介绍) - - [iSCSI 用户组配置说明](./iscsi#iscsi-用户组配置说明) - -13. [AI - OpenClaw](./ai) - - [飞牛 OpenClaw 配置微信 ClawBot 教程](./ai#飞牛-openclaw-配置微信-clawbot-教程) - - [飞牛 OpenClaw 配置钉钉机器人教程](./ai#飞牛-openclaw-配置钉钉机器人教程) - - [飞牛 OpenClaw 配置飞书机器人教程](./ai#飞牛-openclaw-配置飞书机器人教程) - -14. [安全性](./safety) - - [安全披露](./safety#安全披露) - - [安全漏洞报告奖励计划](./safety#安全漏洞报告奖励计划) - -15. [更多](./contact) - - [联系我们](./contact#联系我们) - - [本地账号与飞牛账号的区别?](./contact#本地账号与飞牛账号的区别) - - [管理员和普通用户权限的区别?](./contact#管理员和普通用户权限的区别) - ---- - -# 第一部分:从这里开始 - -## 如何安装和初始化飞牛 fnOS? - -你可以将 fnOS 安装部署到你的设备上,我们支持主流的 x86 架构硬件。以下是安装步骤: - -### 1. 下载安装镜像 - -前往 [飞牛官网](https://www.fnnas.com/download?key=fnos) 下载最新的系统安装 iso 镜像。 - -### 2. 制作引导盘 - -此步骤需要在电脑上使用写盘工具将镜像文件烧录到 U盘中。U 盘在完成安装后就可推出。写盘工具可选择 Rufus ([Rufus4.5 下载 👍](https://static2.fnnas.com/installer/rufus-4.5.exe)、[Rufus 4.0 下载](https://static2.fnnas.com/installer/rufus-4.0.exe)、[Rufus 官网](https://rufus.ie/))、[Balenaetcher](https://etcher.balena.io/) 等。下文以 Rufus 为例: - -1. **选择要写入的 U 盘** -2. **选择下载好的镜像** -3. **点击开始** - -进度条显示 "准备就绪" 即为完成,可拔出 U 盘。 - -### 3. 将系统安装到设备上 - -#### 3.1 设备准备 - -- 将主机接入至少一块硬盘用于做系统盘。系统盘推荐选择 SSD 固态硬盘以获得更快的系统运行速度。 -- 将主机接好网线。暂时不支持只有无线网卡的情况下安装,后续有计划支持。 -- 接好电源、显示器、键盘、鼠标,鼠标非必需。 - -#### 3.2 选择引导盘 - -将制作好的引导盘插入到主机的接口。按主机电源开机,开机同时快速按键盘热键进入boot 设备选择界面。不同设备主板进入boot 页面的热键不同,F2、F7、F8、F10、F11、F12、esc 、Delete 键都有可能。 - -选择引导盘,按回车,进入安装页面。 - -#### 3.3 选择安装方式 - -推荐选择 Graphical Install(图形化安装流程),回车进入下一步。 - -> **注意:** -> - 如果页面提示 "暂不支持在当前模式下安装。请前往BIOS关闭 Secure Boot 后重试",请参考安装系统异常情况处理中第 1 点 -> - 如果在后续图形化安装步骤中遇到错误安装失败,请重启设备、在这一步选择 Rescuing Install(应急模式安装)重试。 - -#### 3.4 选择系统盘 - -方向键切换选择要用于系统安装的硬盘,回车或 Alt+N 进入下一步。 - -#### 3.5 设置系统分区和 Swap 大小 - -在系统盘容量充足的情况下,建议系统分区设置为 64 GB。如安装在虚拟机下,可按情况适当减小。Swap 分区大小已按物理内存大小计算推荐值,可按硬件情况自行调整。 - -> **注意:** 在 fnOS 上,系统将只安装在一块硬盘中。系统盘除系统分区以外的空间,可用于创建存储空间安装应用和存储文件。建议根据需要选择合适容量的 SSD 作为系统盘。 - -确认后,回车或 Alt+N 进入下一步。 - -#### 3.6 确认格式化 - -所选择的系统盘将被格式化,所有内容将被删除,回车或 Alt+O 确认后开始安装。 - -#### 3.7 设置网络 - -安装完成后将自动进入网络设置,系统将自动检测已连接的网卡并获取动态 IP。如网卡未开启 DHCP,你需要手动设置 IP。确认后,回车或 Alt+S 保存设置。 - -#### 3.8 重启设备 - -网络设置保存成功后,你可以将引导盘从主机上拔出。回车或 Alt+O 重启系统。 - -#### 3.9 重启成功进入终端 - -设备重启成功后将自动进入终端登录页,这意味着系统已成功安装并启动!请记住所设置的 IP 地址,接下来你将通过浏览器访问此地址来设置和使用 fnOS。现在你可以将显示器、键盘、鼠标从主机上拔除了。 - -### 4. 浏览器访问 fnOS - -在电脑浏览器上访问上一步所设置的 IP 地址。首次进入 fnOS,你需要完成 2 项简单的系统初始化设置。点击 **开始 NAS 之旅** 进入初始化。 - -#### 4.1 设置设备名称 - -设备名称支持数字、英文及短横线(-),不能以短横线开头或结尾。 - -#### 4.2 设置管理员账号 - -fnOS 用户分为管理员用户和普通用户,且支持创建多个管理员。首位管理员为超级管理员,相较于其他管理员的特殊性在于:创建后不能删除、只能停用,且默认开启 SSH 权限。请设置高强度的密码以提高账号安全性。 - -确认后,点击 **进入fnOS** 完成设置。 - -#### 4.3 进入桌面 - -建议尽快创建存储空间以便上传文件和安装应用。如果硬盘还没有准备好,也可以点击 **稍后** 跳过,后续可从 **设置** > **存储空间管理** 中创建。 - -### 5. 下载安装 App - -推荐下载飞牛 App,具体方式请参考 [如何安装 App 并连接到飞牛 NAS?](./start/install-app.md) - ---- - -## 如何安装 App 并连接到飞牛 NAS? - -本文将介绍如何安装飞牛 App 并且连接到飞牛 NAS。 - -### 1. 下载并安装 APP - -#### iOS 设备 - -1. 打开 AppStore 搜索"飞牛",找到飞牛 App; -2. 下载并安装飞牛 App。 - -#### Android 设备 - -1. 手机浏览器打开飞牛官网,点击"下载 App"开始下载 APK 应用文件; -2. 安装飞牛 App。 - -### 2. 连接到飞牛 NAS - -#### 2.1 同意隐私协议 - -请同意隐私协议以使用飞牛 App。 - -#### 2.2 选择访问方式 - -你可通过以下多种访问方式连接到 NAS: - -**输入 IP 地址、域名或 FN ID** - -- **IP 地址**:安装并且完成初始化系统后,可通过路由器管理中找到飞牛 NAS 的 IP 地址。 -- **DDNS 域名**:如果你在 fnOS 或路由器中配置了 DDNS 域名,可填写域名链接。 -- **FN ID**:如果你在 fnOS 中开启了 FN Connect 远程访问服务,可输入 FN ID 访问到飞牛 NAS,成功登录后,FN Connect 会自动安全地访问到飞牛 NAS。 - -> **注意:** -> - HTTP 访问 fnOS 默认端口为 5666(V0.8.22 版本改为 5666,依旧支持 8000); -> - HTTPS 访问 fnOS 默认端口为 5667(V0.8.22 版本改为 5667,依旧支持 8001); -> - App 登录会根据 HTTPS 开关使用默认端口,如果你在路由器中修改了端口设置,需要在 IP 地址后填写端口以正确访问到飞牛 NAS,比如 `192.168.41.459:端口号`; -> - FN ID 无需填写端口号。 - -**发现局域网内设备** - -- 进入 App 后,点击"发现局域网内设备"可进入查找列表,会自动发现并展示设备同局域网下的飞牛 NAS 设备,选择要连接的 NAS 设备; - -> **注意:** -> - 使用"发现局域网内设备"功能需要开启设备的本地网络访问权限。 - -**历史登录** - -- 如果该设备有历史登录记录,可通过"历史登录"快速填充访问信息及账号信息。 - -#### 2.3 输入登录信息 - -输入飞牛 NAS 的登录账号和密码。 - ---- - -## 如何安装飞牛 TV 并连接到影视服务器? - -### 前置准备 - -1. 需要安装并且初始化飞牛 fnOS 系统; -2. 需要在飞牛 NAS 上,安装飞牛影视应用; -3. 若需要使用 U 盘安装到安卓 TV,需要准备 U 盘用以拷贝飞牛 TV 安装包,并将 U 盘插到 TV 设备安装。 - -### 1. 下载和安装飞牛 TV - -#### 安卓 TV - -以下有 2 种方式可下载安装飞牛 TV 到安卓电视: - -**方式 1:U 盘安装** - -1. 在[飞牛官网](https://www.fnnas.com)下载飞牛 TV 的安装包 (.apk 文件); -2. 将安装包文件转移到 U 盘中; -3. 将带有安装包文件的 U 盘插入 TV 设备,并在设备中安装飞牛 TV。 - -**方式 2:各大应用商店下载安装** - -飞牛 TV 已上架以下安卓 TV 应用商店或电视品牌: -- 当贝市场、创维、酷开、海尔、暴风 -- 欢视商店、TCL、长虹、东芝、三洋 -- OPPO、联想 - -> 注意:由于电视品牌的机型和商店政策差异,可能仅覆盖部分机型。若您所使用的电视或商店不在其中,请选择方式 1(U 盘安装)。 - -#### Apple TV - -1. 打开 Apple TV 中的 App Store; -2. 搜索"飞牛TV",找到飞牛 TV App; -3. 获取并安装飞牛 TV App。 - -### 2. 同意条款以继续使用 - -1. 完成安装后,在"应用"中找到飞牛 TV; -2. 打开飞牛 TV,同意飞牛的**服务条款**和**隐私协议**以继续使用。 - -### 3. 连接影视服务器并登录 - -#### 3.1 连接影视服务器 - -- **自动发现**:打开飞牛 TV 后,自动发现当前局域网下的飞牛影视服务器。 - > 注意:如果没有发现服务器,请检查设备开机情况、网络是否正常、影视服务器是否已创建。 -- **手动添加服务器**:输入飞牛影视服务器的 IP 地址或配置的 DDNS 域名。 - -#### 3.2 登录影视账号 - -- **扫码登录**:打开飞牛 App,进入"首页 > 左上角头像 > 更多 > 扫码",扫描电视上的二维码,并在 App 上同意授权。 -- **账号密码登录**:手动填写影视账号和密码。 - -#### 3.3 成功连接并登录 - -成功连接并登录后,即可进入飞牛 TV。 - ---- - -# 第二部分:存储管理 - -## 如何创建存储空间? - -本文详细介绍了在飞牛 fnOS 系统中创建存储空间的步骤,适用于管理员用户通过网页端或 App 操作。 - -### 创建存储空间的核心步骤 - -#### 1. 选择文件系统 - -支持 Btrfs、ZFS 和 ext4 三种文件系统(自 V0.9.35 版本起)。用户需根据使用需求、硬件性能等因素选择。 - -#### 2. 选择硬盘 - -从左侧列表中选择一个或多个硬盘。 - -> **注意:** -> - 不同类型、接口协议、连接方式或逻辑扇区大小的硬盘不能混合组存储空间。 -> - 系统盘剩余容量可单独创建存储空间,但不能组 RAID 或扩容。 -> - 外接硬盘需确保接口牢固,避免因松动导致存储空间异常。 - -#### 3. 选择存储模式 - -根据所选文件系统和硬盘数量,系统会动态显示可用的存储模式(如 RAID 类型)。 - -确认预计容量后点击"下一步"。 - -#### 4. 设置用户权限 - -- **管理员**:默认无容量上限,如需限制需在"用户管理"中单独设置。 -- **普通用户**:可指定允许使用的用户及其容量上限。 - -#### 5. 确认信息 - -可填写存储空间描述(可选),并选择是否创建后自动执行硬盘读写检测。 - -#### 6. 确认格式化 - -系统会提示硬盘将被格式化,所有数据丢失。需输入管理员密码确认。 - -#### 7. 创建成功 - -完成上述步骤后,存储空间即创建成功。 - -### 关键注意事项 - -- **兼容性**:硬盘需满足类型、接口等一致性要求。 -- **系统盘限制**:系统盘剩余空间不能用于 RAID 或扩容。 -- **外接硬盘风险**:需确保接口稳定,避免因松动引发问题。 -- **权限管理**:管理员可全局访问存储空间,普通用户需单独授权。 - ---- - -## 硬盘会自动休眠吗? - -fnOS 支持硬盘在闲时自动休眠,包括除了系统盘外的内置盘和支持休眠的外接盘。默认开启,修改设置方式如下: - -使用管理员账号打开网页端 **设置** > **硬盘信息** > **硬盘休眠**,设置内置和外接硬盘进入休眠的时间。 - -### 硬盘自动唤醒偏好 - -自系统版本 v0.9.10 起新增支持设置**硬盘自动唤醒偏好**。当开启了硬盘休眠后,可选择硬盘唤醒的策略: - -#### 1. 按需唤醒:仅在对存储空间读写数据时 - -选择此选项后,系统只在读写数据时唤醒硬盘,不被读写的硬盘仍将保持休眠状态。 - -#### 2. 响应更快:有用户登录即唤醒 - -选择此选项后,除了读写数据会唤醒硬盘外,有用户登录网页端、App时,系统也将自动唤醒全部硬盘,以提升读写操作时的响应速度,使用体验更丝滑。 - -> **注意:** -> - 常见会触发读写硬盘的操作有: -> - 访问文件管理; -> - 硬盘所在的存储空间上有正在执行的文件任务,包括上传、下载、移动、复制等; -> - 应用或 Docker 安装在硬盘所在的存储空间上,且正在运行产生传输; -> - 应用或 Docker 正在访问存储空间上的文件,包括影视刮削播放、相册查看和 AI 识别等; -> - 下载类应用或 Docker 容器中有任务正在下载或做种。 -> - 除了读写文件外,访问 **系统设置 > 存储空间管理** 和 **硬盘信息** 页面时,由于需要查询存储相关信息,也会唤醒已被用于存储空间的所有硬盘。如不希望唤醒,可通过 **资源监控** 查看硬盘状态和信息。 - ---- - -## 如何使用 SSD 缓存加速? - -SSD 缓存加速功能是指将固态硬盘 (SSD) 作为存储空间的缓存,从而将频繁访问的数据缓存到高速 SSD 上,以此提升日常使用中的读写性能。 - -### 注意事项 - -1. 创建 SSD 缓存的前提条件是:有至少1 块内置的固态硬盘 (SSD) 未被用于创建存储空间,且容量大于10G。系统盘除系统分区外的剩余空间如果满足上述条件,也可以被用于创建 SSD 缓存。这将增加系统盘的读写,因此可能缩短系统盘的寿命,请谨慎选择。 -2. 一个 SSD 缓存可以被分配给多个存储空间。创建时分配容量有剩余的,创建完成后可以继续分配给其他存储空间。 -3. 多块硬盘组成 SSD 时可选择阵列模式,以获得数据保护或速度上的提升。 -4. 有 2 种缓存模式可选:只读缓存和读写缓存(ZFS 下为写入缓存)。不稳定的盘做读写缓存容易造成数据丢失、导致整个存储空间损坏,请谨慎选择。 -5. 如要删除读写模式的 SSD 缓存,请务必从界面上操作,切勿直接将 SSD 从设备中拔出,否则数据可能会丢失。 -6. 暂不支持对已经创建好的缓存进行扩容、更换硬盘、修改存储模式等操作,如有需要,请删除缓存后重建。 - -### 创建 SSD 缓存 - -登录管理员账号,从桌面打开 **系统设置** > **存储空间管理** > **SSD 缓存加速** ,点击 **创建SSD 缓存**。 - -#### 1. 选择存储空间 - -选择要使用 SSD 缓存的存储空间。 - -请注意,对于 Btrfs 和 ext4 的存储空间,虽然创建时只能选择一个存储空间,但如果分配后缓存的剩余容量大于 10G,你仍可将剩余容量分配给其他存储空间,分配方式详见下文《3. 分配 SSD 缓存容量》。而对于 ZFS 的存储空间,则无法多个存储空间共用一个 SSD 缓存。 - -#### 2. 选择缓存模式 - -对于 Btrfs 和 ext4 的存储空间,可选择只读缓存或读写缓存。只读缓存只缓存常读取的数据,适用于读多写少的场景。读写缓存适用于高频写入的场景。 - -对于 ZFS 的存储空间,可选择只读缓存(L2ARC)或读写缓存(SLOG)。ZFS 默认使用物理内存作为一级缓存(ARC)。添加 L2ARC 将加速常用文件读取,适合读多写少的场景,但将增加内存占用。SLOG 将加速同步写入操作,适合频繁写入或虚拟化等需要高数据一致性的场景。 - -#### 3. 选择硬盘和存储模式 - -在页面左侧选择要用于创建 SSD 缓存的 1 个或多个硬盘。根据所选的硬盘个数,右侧将动态显示可供选择的存储模式。选择存储模式后,可以在弹窗底部确认缓存的预计容量情况。确认无误后,点击 **下一步**。 - -> 注意:如果缓存的存储模式不具备数据保护能力(如 Basic、RAID 0),且选择了读写缓存(或 ZFS 的写入缓存)模式,则在 SSD 损坏、掉盘或非正常移除时,有较大概率导致数据丢失,请谨慎选择。 - -#### 4. 分配缓存容量 - -对于 Btrfs 和 ext4 的存储空间,可指定要分配给此存储空间的缓存容量,剩余容量可分配给其他存储空间。 - -#### 5. 确认信息 - -确认所选的存储空间、缓存模式、硬盘及存储模式、分配容量等信息。 - -#### 6. 确认格式化 - -用于组缓存的硬盘将被格式化,所有数据将被删除。请输入管理员密码进行最终确认。 - -#### 7. 创建成功 - -缓存创建完成后,可以在 SSD 缓存加速页面查看缓存状态。 - -### 分配 SSD 缓存容量 - -对于 Btrfs 和 ext4 的存储空间,多个存储空间可共用同个 SSD 缓存。当 SSD 缓存存在未分配的分区时,点击 **分配**,选择存储空间、缓存模式及分配容量,点击确认。输入管理员密码,点击开始分配。分配成功后,缓存容量将被分配给指定存储空间。 - -### 删除 SSD 缓存 - -当缓存仅被分配给一个存储空间时,点击 **缓存** > **更多** > **删除**,可删除缓存。当缓存被分配给多个存储空间时,点击 **缓存空间容量分配** > **移除**,分别将所有分区移除后,缓存将自动被删除。请注意,移除读写缓存时需要将已缓存的数据回写到存储空间中,这可能会需要一些时间。 - ---- - -# 第三部分:飞牛影视 - -## 如何安装飞牛影视和创建媒体库? - -### 1. 安装飞牛影视 - -#### 步骤1:通过应用中心安装 - -使用管理员账号登录网页端,打开"应用中心",找到"影视"应用并点击安装。 - -#### 步骤2:确认安装位置 - -默认安装在系统分区,需指定媒体元数据存储位置(后续可修改)。 - -#### 步骤3:检查信息并确认安装 - -#### 步骤4:安装完成 - -桌面图标更新为已安装状态。 - -### 2. 设置影视可访问的文件范围 - -**入口**:通过应用中心或系统设置中的"影视"应用管理。 - -**添加文件夹**:可选择"我的文件""存储空间""外接存储""远程挂载"等路径,并设置为读写权限。 - -**保存设置**:点击"确定"保存,若路径异常需参考相关问题处理。 - -### 3. 影视初始化 - -#### 3.1 打开方式 - -通过桌面图标、App或浏览器直接访问(NAS地址后加`/v`)。 - -#### 3.2 授权登录 - -使用NAS管理员账号授权登录。 - -#### 3.3 创建影视管理员账号 - -设置独立的影视管理员用户名和密码(与NAS账号体系独立)。 - -#### 3.4 服务器设置 - -- 设置服务器名称 -- 首选语言 -- 元数据存储位置 -- 实时监控文件更新等 - -### 4. 创建媒体库 - -#### 4.1 基础信息 - -选择内容类型(如电影、电视剧等),创建后不可修改。 - -#### 4.2 媒体文件夹 - -添加已授权访问的文件夹路径。 - -#### 4.3 高级设置 - -- 元数据下载语言(中文/英文) -- 是否允许成人内容(基于TMDB标签) -- 自动下载字幕及字幕语言设置 - -#### 4.4 创建成功 - -系统自动开始扫描文件、刮削元数据、下载字幕等。 - -### 关键注意事项 - -- 影视应用的用户权限与NAS权限独立,管理员需单独设置文件访问范围。 -- 朋友仅需影视账号,家人可同时使用NAS账号登录影视。 -- 成人内容过滤依赖TMDB标签,部分未标记内容可能无法完全屏蔽。 - ---- - -## 如何为家人朋友开通影视账号? - -### 1. 影视独立账号 - -#### 1.1 如何开通? - -- 使用影视管理员账号登录影视应用。 -- 点击首页右上角 → 设置 → 影视服务器 - 用户 → 新增用户。 -- 设置用户名、密码,并选择可访问的媒体库范围。 -- 点击确认保存。 - -#### 1.2 如何登录? - -- **网页端**:在浏览器中输入NAS地址后加 `/v`,使用影视独立账号登录。 -- **App端**:目前不支持独立账号登录,后续计划推出独立影视App。 -- **TV端**:参考安装指南。 - -### 2. NAS账号登录 - -#### 2.1 哪些NAS用户可以访问影视? - -- 默认所有NAS用户均可访问影视应用。 -- 可修改为仅管理员可见: - 1. 使用NAS管理员账号进入设置 → 应用 → 影视。 - 2. 将"哪些用户可以从桌面访问"设置为"仅管理员"。 - 3. 点击确定保存(注:此设置仅限制桌面图标显示,不影响通过浏览器直接访问)。 - -#### 2.2 如何用NAS账号登录? - -- **网页端/TV端**:在影视登录页选择"使用NAS账号登录",跳转至NAS授权页面确认。 -- **App端**:同网页端流程。 - -#### 2.3 用NAS账号登录的用户可访问哪些媒体库? - -- 新用户默认无媒体库访问权限,需由影视管理员设置: - 1. 使用影视管理员账号进入设置 → 影视服务器 - 用户 → 新用户默认权限设置。 - 2. 选择可访问的媒体库范围并保存。 -- 此设置影响后续所有NAS新用户的默认权限,支持按需个性化调整。 - -### 关键说明 - -- 影视账号与NAS账号为两套独立体系,可根据需求选择开通类型。 -- 影视独立账号适用于仅需影视服务的用户;NAS账号适用于需使用全功能(如文件存储、相册等)的用户。 -- 权限管理灵活,支持按用户或媒体库细分访问范围。 - ---- - -## Apple TV 上如何通过 Infuse 使用飞牛影视? - -[Infuse](https://firecore.com/infuse) 是 Apple 生态下的一款播放器,适用于 iPhone、iPad、Apple TV 和 Mac 。由于飞牛影视暂未发布 Apple TV 版本,有需要的用户可在 Apple TV 上自行安装 Infuse ,并按照本文指引连接飞牛影视,达到以下使用效果: - -- 从 Infuse 上查看飞牛影视上的媒体库,显示由飞牛刮削的影片信息 -- 播放影片,支持切换音频,支持切换内置、外挂字幕,播放记录与飞牛客户端同步 -- 通过搜索、按类型筛选、排序等方式查找影片 - -> **注意:** -> - 此功能需将飞牛影视应用升级至 V0.8.17 及以上版本,Apple tvOS 和 iOS 需升级到18 版本,Infuse 建议升级到最新版本。 -> - 飞牛影视对 Infuse 的主要功能做了兼容处理。由于二者在功能上存在客观差异,在 Infuse 上使用与在飞牛上使用的体验将略有差别。 - -### 1. Infuse 连接飞牛影视服务器 - -#### TV - 方式 1:局域网内扫描发现(Apple TV) - -1. 打开 Infuse,点击右上角 **设置** ,点击 **共享** -2. 点击 **添加媒体服务器** -3. 在 **可用服务器** 中找到带有"飞牛影视"前缀的服务器,点击选择 -4. 输入飞牛影视的用户名和密码 -5. (可选)点击 **高级设置** > **媒体库模式** 开关,打开后 Infuse 首页将按照 Infuse 方式聚合展示飞牛上的影片 -6. 点击 **保存** 确认连接,连接成功后首页将出现飞牛影视服务器上的媒体库 - -#### TV - 方式 2:手动连接(Apple TV) - -1. 打开 Infuse,点击右上角 **设置** ,点击 **共享** -2. 点击 **添加媒体服务器** -3. 在 **可用服务器** 中选择 **其他 Jellyfin** -4. 输入飞牛影视服务器的 IP、用户名和密码,(可选)将名称修改为便于识别的名称 -5. 点击 **高级设置** , **端口** 输入 8005 -6. (可选)**媒体库模式** 开关,打开后 Infuse 首页将按照 Infuse 方式聚合展示飞牛上的影片 -7. 点击 **新增** 确认连接,连接成功后首页将出现飞牛影视服务器上的媒体库 - -#### App - 方式 1:局域网内扫描发现(iPad / iPhone) - -1. 打开 Infuse,点击右下角 **设置** ,点击 **新增文件来源** -2. 在 **可用的共享** 中找到带有"飞牛影视"前缀的服务器,点击选择 -3. 输入飞牛影视的用户名和密码 -4. (可选)点击 **高级设置** > **媒体库模式** 开关 -5. 点击 **新增** 确认连接 - -> **注意:** -> - 当局域网内有其他 Emby 或 Jellyfin 服务器时,将可能无法扫描到飞牛影视服务器。请改用手动连接方式。 -> - 如果飞牛设备有多个网卡,请检查扫描到的服务器 IP 是否符合预期。如果不是,请改用手动连接方式连接目标 IP 。 - -#### App - 方式 2:手动连接(iPad / iPhone) - -1. 打开 Infuse,点击右下角 **设置** ,点击 **新增文件来源** -2. 共享列表滚动到底部,点击 **媒体服务器** > **其他 Jellyfin** -3. 输入飞牛影视服务器的 IP、用户名和密码 -4. 点击 **高级设置** , **端口** 输入 8005 -5. (可选)**媒体库模式** 开关 -6. 点击 **新增** 确认连接 - -### 2. 飞牛影视与 Infuse 体验差异 - -在 Infuse 上使用飞牛影视,相较于直接使用飞牛影视客户端: - -- 同个影片或同一集的多个版本不会聚合,将平铺展示(在 **高级** 中打开 **媒体库模式** ,则从首页访问时会聚合) - -相较于在 Infuse 上直接挂载影视目录或使用其他服务器: - -- 部分排序方式不支持,如按文件夹、按播放次数等 -- 不支持安装 InfuseSync 插件 - ---- - -# 第四部分:相册 - -## 如何备份手机照片? - -通过飞牛 App 的 **相册备份** 功能,您可以将手机本地的照片备份上传到飞牛 NAS 上,以节省手机空间。 - -> **前置准备:** -> 1. 确保已经安装 **相册** 应用; -> 2. 确保 **备份位置** 的存储空间足够,如果不足,请 **修改备份位置**; -> 3. 下载安装飞牛 App,连接到飞牛 NAS; -> 4. 手机设备需要给飞牛 App 开启本地照片或文件的访问权限。 - -### 操作步骤 - -1. 打开飞牛 App → **相册** → **设置** → **备份设置**; -2. 开启 **相册自动备份** 功能开关。开启备份后,可查看到 **备份状态**: - -> **备份状态说明:** -> 1. **扫描中**:正在扫描手机设备本地的照片和视频数量以及大小,准备备份上传。 -> 2. **正在备份**:正在备份上传,会显示剩余数量以及已完成备份上传的数量。 -> 3. **备份成功**:该次备份已完成,全部成功或部分失败。如果部分失败,点击 **备份状态栏** 可跳转至 **任务传输** 查看详细错误信息。 -> 4. **备份暂停或失败**:可能存在以下原因: -> - **电量不足**:如果有开启 **节能备份**,当手机设备电量低于 15% 时,将暂停备份。 -> - **没有开启 Wi-Fi**:如果没有开启 **使用流量备份**,在非 Wi-Fi 环境下将会暂停备份。 -> - **连接断开**:如果网络环境变换,导致与飞牛 NAS 的连接断开,备份将暂停。 -> - **存储空间不足**:如果 **备份位置** 的存储空间不足。 -> - **存储空间无权限**:请联系管理员处理。 - -3. 如果在备份的过程中关闭备份,将取消正在备份以及未备份的队列任务,但已完成备份的照片及视频将保留。 - -### 备份规则 - -- **备份所有**:将扫描和备份设备中的所有照片和视频,将跳过已备份的照片,不会重复备份。 -- **备份新照片**:仅备份从现在起新拍摄的照片和视频。 - -**继续备份** 将在上一次备份规则的基础上继续备份。 - -### 备份目录偏好 - -#### 苹果设备 - -- **按拍摄日期**:按照片的拍摄日期,创建并保存到子文件夹。"备份位置/年/月/" -- **保存在一个文件夹下**:所有照片都保存在一个文件夹下。 - -#### 安卓设备 - -- **默认**:区分照片的目录,DCIM 目录下的照片按拍摄日期保存。 -- **按拍摄日期**:按照片的拍摄日期保存。 -- **按设备目录**:按当前设备的文件夹目录结构直接保存。 -- **保存在一个文件夹下**:所有照片都保存在一个文件夹下。 - -### 不熄屏备份 - -开启备份之后,若需要备份大量照片和视频,可开启 **不熄屏备份**,允许飞牛相册在屏幕全暗的状态下持续备份上传直至完成。 - -> 若要保持 **不熄屏备份**,请注意: -> - 保持稳定的网络连接; -> - 保持设备在充电状态; -> - 请勿关闭手机屏幕或退出 App。 - -### 其他备份设置 - -- **按拍摄日期重命名**:上传时按照"IMG_年月日_时分秒"的格式重命名文件。 -- **同步创建相册**:上传、备份时,根据已有的手机相册同步在飞牛相册应用中创建相册。 - ---- - -## 如何共享相册给设备内其他用户? - -你可以在相册应用中通过 **共享相册** 功能将相册共享给设备内的其他用户,可以设置公开给设备内所有用户或者仅共享给指定用户。 - -### 1. 管理员创建用户 - -使用管理员账号,在 **设置** > **用户管理** > **新增用户** 可以创建用户或者用户组。 - -### 2. 共享相册 - -你可以通过 2 种方式,将相册共享给设备内的其他用户。 - -#### Web 端操作 - -**1. 共享相册(Web)** - -在相册详情中,点击 **共享** 可设置将相册 **共享给此设备的所有账号** 或者 **共享给指定账号**。 - -> 注意:相册共享可选择设置 2 种权限 -> - **可查看** 权限:被共享者可查看相册、预览内容详细、可下载内容。 -> - **可添加** 权限:被共享者在拥有 **可查看** 权限基础上,可添加内容进入相册、可从相册中移除内容。 - -**2. 共享照片将自动创建共享相册(Web)** - -在 **图库**、**相册详情**、**预览详情** 或 **搜索结果** 中,选中多个或者单个照片进行 **共享**。此方式共享,会以当前日期命名创建 **共享相册**,并且显示在 **我的共享** 中。 - -#### App 端操作 - -**1. 共享相册(App)** - -在相册详情中,点击 **共享** 可设置将相册 **共享给此设备的所有账号** 或者 **共享给指定账号**。 - -**2. 共享照片将自动创建共享相册(App)** - -在 **图库**、**相册详情**、**预览详情** 或 **搜索结果** 中,选中多个或者单个照片进行 **共享**。 - ---- - -## AI 相册功能说明 - -### 一、安装说明 - -#### 1.1 硬件配置要求 - -- **内存**:最低 2 GB(智能识别增强模型需 8 GB) -- **存储空间**:模型安装位置需 ≥20 GB - -#### 1.2 下载安装模型 - -- **人脸识别**:仅支持"标准模型" -- **智能识别**:可选"基础模型"(内存≥2GB)或"增强模型"(内存≥8GB) - -#### 1.3 GPU 设置 - -- **支持显卡**:Intel GPU、NVIDIA(GeForce 700以上) -- **常见问题**:驱动缺失、显存不足、GPU繁忙或老旧显卡不兼容 - -### 二、任务执行 - -- **触发方式**:安装模型后自动执行、上传文件后自动触发、手动执行未识别内容、重置识别任务 -- **任务失败处理**:检查GPU兼容性、存储状态、文件权限,或等待系统资源空闲后重试 - -### 三、AI 相册功能详解 - -#### 3.1 人脸识别高级设置 - -- **置信度阈值**:影响人脸检测准确度(不建议修改) -- **最少照片数**:控制生成人物相册的最低照片数量 -- **相似度差异阈值**:调整人物聚合的精确度(可选"宽松聚合") - -#### 3.2 人物管理 - -支持命名、合并人物、隐藏/显示人物、手动添加面孔。 - -#### 3.3 AI 搜索 - -支持自然语言搜索(如"在海边散步""吃披萨")。开启"视频增强识别"可提升视频搜索效果。 - -#### 3.4 智能分类 - -自动根据照片/视频内容生成分类标签(如地点、活动)。 - -#### 3.5 视频增强识别 - -- 仅支持 **.mp4、.mov** 格式 -- 占用资源较多,建议在完成基础识别后单独启用 - -### 注意事项 - -- 模型文件需通过官方渠道下载,避免安装失败 -- 任务执行时避免同时运行其他高负载应用 -- 视频增强识别耗时较长,需耐心等待 - ---- - -# 第五部分:远程访问 - -## 如何远程访问到飞牛 NAS? - -飞牛 fnOS 有提供公网 IP 直连、FN Connect 远程访问服务、DDNS 域名转发、P2P 等多种远程访问方式。 - -### 方法1:公网 IP 直连 - -你可以与运营商申请公网 IP,并且在路由器中配置端口转发。打开网页浏览器或者在飞牛 App 上直接输入公网 IPv4 地址或 IPv6 地址以及端口号即可访问。 - -> **注意**: -> - 飞牛 fnOS 的 HTTP 访问端口为 8000,HTTPS 访问端口为 8001; -> - 若有配置端口转发,必须要在访问地址后增加端口号才能正确访问到飞牛 NAS; -> - 公网 IP 直连的网络速度无折损,但公网 IP 直连无安全证书,HTTP 访问明文传输,请在安全的网络环境下访问。 - -### 方法2:配置 DDNS 域名和证书 - -DDNS 服务将自动定时更新你的飞牛 NAS 的出口 IP,并更新 IP 映射域名,通过域名即可轻松访问到飞牛 NAS。在此基础上,配置对应域名的 SSL 证书,可通过 HTTPS 安全访问到你的飞牛 NAS。 - -> **注意**: -> - 购买并配置域名及证书; -> - 飞牛支持以下服务商的 DDNS 服务:腾讯云、阿里云、华为云、花生壳、DuckDNS; -> - 推荐配置 DDNS 域名及证书,速度无损且安全。 - -### 方法3:FN Connect 远程访问服务 - -FN Connect 是由飞牛自研的远程访问服务,可以为你提供安全、高效远程访问到飞牛 NAS 的服务。使用本服务,你需创建唯一的 FN ID 以标识你的飞牛 NAS 设备。 - -> **注意**: -> - 使用 FN Connect 服务,你将被要求注册或登录飞牛账号; -> - FN Connect 服务将为你的飞牛 NAS 创建 FN ID 对应的**唯一子域名的 SSL 证书**,可安全地访问到飞牛 NAS; -> - FN Connect 将根据当下你所处的网络环境,判断选择最优的访问方式自动连接; -> - FN Connect 中继转发服务会产生流量成本,因此会有**限速**。 - -### 方法4:客户端 P2P 连接 - -使用飞牛 App,在使用 FN Connect 服务的基础下,如果网络的 IPv6 可以通,则会自动切换到 P2P 方式直连到你的飞牛 NAS。 - -> **注意**: -> - P2P 连接状态下,可能因为网络不稳定而回退至**中继转发**。 -> - P2P 连接,没有经过飞牛服务器,而是飞牛 NAS 设备与手机客户端直接加密通信,因此**安全无限速**。 - ---- - -## 如何管理 FN Connect 权益? - -### 1. 绑定操作 - -#### 1.1 购买后直接绑定 - -- 单台 NAS:自动绑定 -- 多台 NAS:购买后弹出绑定弹窗选择设备 -- 首次绑定需要3分钟线路匹配时间 - -#### 1.2 购买后延迟绑定 - -- 通过"我的账号-我的设备"进入设备管理 -- 支持选择多个未生效权益进行绑定 -- 一台NAS只能绑定一个已生效权益 - -#### 1.3 调整权益激活顺序 - -- 可拖动调整未生效权益的顺序 -- 已生效权益固定为第一位 - -### 2. 解绑操作 - -- 未生效权益解绑后仍为未生效状态 -- 已生效权益解绑后会持续计算使用时长 -- 解绑后会自动激活下一个未生效权益 - -### 3. 换绑操作 - -- 支持两台NAS之间已生效权益的对调 -- 不影响未生效权益的绑定情况 - -### 4. 其他重要说明 - -- 权益归属于购买时的飞牛账号 -- 设备重装或解绑不会丢失权益 -- 3天内支持无责任退款 -- 带宽问题可通过订单咨询提交工单 - -### 关键注意事项 - -- 权益绑定后需要3分钟线路匹配时间 -- 一台NAS只能有一个已生效权益 -- 解绑已生效权益需谨慎操作 -- 权益与账号绑定,不随设备变化而丢失 - ---- - -# 第六部分:系统设置 - -## 如何创建新用户? - -在 fnOS 上,你可以创建多个用户账号,方便多人共同使用 NAS。 - -### 1. 设置入口 - -使用管理员账号,打开 **设置 > 用户管理 > 用户**,点击 **新增用户**。 - -### 2. 账号信息 - -- **用户类型**:区分管理员和普通用户 2 种角色权限。 -- **用户名**:支持中文、英文、数字,不能包含特殊符号和空格。 -- **备注**:可选填写。 -- **密码**:不少于 6 位,至少包含大写字母、小写字母、数字、符号中的 2 种。点击 **使用随机密码**,将填入随机密码并自动复制到剪贴板。 - -### 3. 加入用户组 - -可选择是否要将新用户加入到已有的用户组中。如需创建用户组,请前往 **设置 > 用户管理 > 用户组 > 新增用户组**。 - -### 4. 设置可用存储空间及配额 - -你可以按单个用户设置允许其使用哪个存储空间,不希望该用户使用的存储空间,可点击 **×** 按钮移除。管理员用户允许使用系统上的所有存储空间。 - -每个存储空间下该用户可使用的容量默认无限制,可手动输入数值修改。 - -### 5. 创建完成 - -后续可在用户详情中查看该用户的存储空间用量,进行修改、停用、删除等操作。 - ---- - -## 如何使用无线网卡? - -fnOS 支持无线网卡,你可通过《 fnOS 支持网卡范围验证表》查看目前已验证兼容的网卡型号。 - -### 1. 启用无线网卡 - -使用管理员账号,打开 **系统设置** > **网络设置**,可以看到已接入的无线网卡。点击 **更多** > **启用无线网卡**。 - -### 2. 连接网络 - -启用后将自动扫描局域网内的网络。点击要连接的网络,输入密码,点击确定。 - -如果网络未被扫描发现,或为隐藏网络,你可以点击 **手动添加**,输入网络名称和密码来连接。 - -### 3. 切换网络 - -选择无线网卡,点击 **更多** > **编辑**,打开网络列表,点击或手动添加要切换的网络。 - -> **注意:** -> 切换网络后IP将发生变化,可能会导致当前界面连接断开。建议: -> - 若当前有其他已连接的网络,请记住其IP,在切换网络后通过该 IP 访问; -> - 若已开启 FNConnect 服务,在切换网络后可通过 FNID 访问; -> - 若你当前和设备位于同个局域网,在切换网络后可通过飞牛 App 扫描局域网内的设备以获取新 IP。 - ---- - -## 如何修改飞牛系统的端口? - -fnOS 版本更新到 V0.8.22 版本之后,在 **设置** 中可以修改飞牛NAS的 **HTTP 和 HTTPS 端口**、设置 **强制 HTTPS 连接**、设置是否固定占用并 **重定向 80 与 443 端口**。 - -### 1. 默认端口 - -fnOS 从 V0.8.22 版本之后,默认端口修改为 **HTTP 5666** 和 **HTTPS 5667** 端口。 - -> **注意:** -> - 在公测阶段飞牛仍继续占用 8000 和 8001 端口,允许用户通过 8000 和 8001 端口访问到飞牛系统。 -> - 在正式版本之后将不再使用 8000 和 8001 端口。 - -### 2. 如何使用不同端口号访问 fnOS? - -#### 网页 - -在网页浏览器的地址栏中输入 `http://192.168.1.10:5666`(IP 地址例子) - -> **注意:** -> - 如果已开启了 **重定向 80 与 443 端口**,则可以不需要填写端口号。 - -#### App - -在访问方式中输入 `http://192.168.1.10:5666`(IP 地址例子) - -> **注意:** -> - **如果不填写端口,App 和 TV 会默认跳转 HTTP 8000 和 HTTPS 8001 端口**。 - -### 3. 如何修改端口 - -系统升级到 V0.8.22 版本之后,在 **设置 - 安全性** 中可以设置端口。 - -> **注意:** -> - HTTP 和 HTTPS 端口号不可为空,部分端口号不可用。 -> - 确认修改端口,将会导致正在上传的任务或者正在进行的服务中断。 - -#### 强制 HTTPS 连接 - -启用了**强制 HTTPS 连接**之后,系统将 HTTP 连接重定向到 HTTPS 连接。HTTPS 连接,浏览器将会验证安全、有效的证书才能正常访问。 - -#### 重定向 80 与 443 端口 - -系统将默认启用 80 与 443 端口。启用之后,若你访问 80 或 443 端口,系统将 80 与 443 端口分别重定向到飞牛系统的 HTTP 端口 和 HTTPS 端口。 - ---- - -# 第七部分:文件管理 - -## 如何将我的文件共享给其他用户? - -在 fnOS 上,你可以在多个用户之间共享位于 NAS 存储空间上的文件。文件所在的位置不同,共享的方法也有所差别: - -1. 文件位于 **我的文件** 下,即存放于某个用户的个人目录中:目录所有者可通过 **共享给设备的用户** 功能,允许其他用户从 **他人共享** 中访问其个人目录下的文件。 - -2. 文件位于 **团队文件夹** 中,即存放于存储空间下的 @team 目录中,不归属于任何用户:管理员可对团队文件夹及其子文件夹或文件设置用户/用户组的访问权限。 - -### 如何将我的文件共享给他人? - -1. 选择 **我的文件** 下的任一文件夹,右键,点击 **共享给设备内的用户**,打开共享设置弹窗。 -2. 在页面左侧选择要共享给哪些用户或用户组,可搜索名称或备注。 -3. 在右侧选择被共享者的访问权限,可选择只读、读写或禁止访问。选择后,点击确认。 -4. 设置共享名称,此名称将展示给被共享者。默认为文件夹名称。确认后,点击开启共享。 - -### 如何修改共享权限? - -1. 打开 **文件管理 > 我的共享**,找到要修改的共享文件夹,右键点击共享设置。 -2. 修改权限,或新增用户,点击确定。 -3. 选择是否要将此次修改应用到已存在的子文件夹和文件上。 - -### 被共享者如何访问他人共享的文件? - -1. 打开 **文件管理 > 他人共享**,双击共享者。 -2. 即可查看到被共享的文件夹。 - -### 如何关闭共享? - -1. 打开 **文件管理 > 我的共享**,找到要修改的共享文件夹,右键点击共享设置。点击关闭共享。 -2. 关闭共享的本质是移除已为其他用户添加的权限。 - ---- - -## 如何创建文件的外部分享链接? - -将 fnOS 更新到 V0.8.26 版本之后,你可以在 **文件管理** 中创建文件的 **外部分享链接**,你的家人朋友可以通过此链接访问并且下载你分享的文件。 - -### 如何设置外分享链接? - -打开 **文件管理**,在左下角可打开 **文件管理设置** - **外链分享设置**。 - -你可以在 **外链分享设置** 中,设置以下功能: - -- **外链分享权限**:可以选择 **所有用户** 或 **仅管理员** 都能够使用 **外链分享** -- **分享链接设置**: - - **默认链接**:若你有开启 FN Connect 远程访问服务,则每次分享时会生成 FN Connect 专属的文件外链分享链接。 - - **自定义链接**:可设置生成的外部分享链接使用 HTTPS 或 HTTP 协议。 - -### 如何创建外部分享链接? - -在 **文件管理**,可选中文件,点击右键或更多,创建外部分享链接。 - -创建外部分享链接时,你可以设置: - -- **到期时间**:若不勾选,则为 **永久**。 -- **密码**:若不勾选,则为 **无密码**,若勾选,请输入至少4位的密码。 -- **可访问次数**:默认不勾选,若不勾选,则为 **无限制次数** - -点击确定,即可创建外部分享链接。同一个文件可分别创建多个不同的分享链接。 - -### 如何管理外分享链接? - -你可以在 **文件管理** - **外链分享** 中看见并管理的所有你创建的文件外部分享链接。 - -文件外分享链接,有以下 3 个状态: - -1. **有效**:未达到期时间、可访问次数不为0、当前用户有使用功能的权限。 -2. **失效**:时间已经到了到期时间 或者 可访问次数 = 0,则此链接失效。 -3. **无效**:用户无外链分享功能的使用权限等异常情况会导致链接无效。 - ---- - -## 如何共享团队文件? - -本文详细介绍了在飞牛 fnOS 系统中如何共享团队文件,包括团队文件夹的创建、权限设置、成员管理以及临时共享子文件的方法。 - -### 1. 团队文件夹的创建 - -- 登录管理员账号,进入"我的文件 > 团队文件",点击"新建团队文件夹"。 -- 填写文件夹名称、选择存储空间,并设置容量上限、文件共享协议权限及回收站管理权限。 -- 设置成员及权限(支持按用户、用户组或应用设置只读、读写或禁止访问)。 - -### 2. 修改团队文件夹的成员权限 - -- 在"团队文件"中找到目标文件夹,右键选择"成员权限"进行修改。 -- 支持将权限变更应用到现有子文件夹和文件。 - -### 3. 收紧子文件权限 - -- 针对特定子文件夹或文件,可通过"详细信息 > 权限"设置独立于父级的权限。 -- 适用于限制用户访问部分内容的情况。 - -### 4. 临时共享子文件给非团队成员 - -- 对子文件设置权限后,复制其原始路径发送给被共享者。 -- 被共享者可通过粘贴路径直接访问,但无权限浏览上层目录。 - -### 关键注意事项 - -- **权限继承规则:** - - 用户组权限由组内成员继承,多组权限取并集。 - - 用户单独设置的权限优先级高于用户组权限。 -- **功能依赖:** 团队文件功能需 fnOS V0.9.35 或更高版本支持。 - ---- - -# 第八部分:文件共享协议 - -## Windows 通过 WebDAV 挂载飞牛注意事项 - -### 一、开启 HTTP 支持 - -Windows 默认不支持通过 HTTP 协议连接 WebDAV 服务,需要通过修改注册表来开启。具体步骤如下: - -1. 使用快捷键 Win + R 打开运行窗口,输入 `regedit` 打开注册表编辑器。 -2. 在地址栏中粘贴路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters`,然后按回车键。 -3. 找到 `BasicAuthLevel` 项,双击打开修改窗口,将数值数据改为 `2`,然后点击确定。 -4. 再次使用 Win + R 打开运行窗口,输入 `services.msc` 打开服务窗口。 -5. 找到 `WebClient` 服务,右键选择"重新启动"。 - -### 二、解决文件大小超出限制的问题 - -Windows WebDAV 默认传输文件大小限制为 50MB,可以通过修改注册表来调整这一限制。具体操作如下: - -1. 使用 Win + R 打开运行窗口,输入 `regedit` 打开注册表编辑器。 -2. 在地址栏中粘贴路径:`计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters` -3. 找到 `FileSizeLimitInBytes` 项,双击打开修改窗口,将默认值 `2faf080`(50MB)修改为 `ffffffff`(4GB) -4. 再次使用 Win + R 打开运行窗口,输入 `services.msc` 打开服务窗口。 -5. 找到 `WebClient` 服务,右键选择"重新启动"。 - -### 其他说明 - -- 文章还提到了在 Windows 上挂载 WebDAV 的其他方式,例如使用第三方文件管理客户端(如 RaiDrive、WinSCP、FileZilla)。 - ---- - -## Windows 通过 NFS 挂载飞牛注意事项 - -### 1. 开启 NFS 服务 - -- **目的:** 在 Windows 系统中启用 NFS 客户端功能,以便能够通过 NFS 协议挂载飞牛 NAS。 - -- **步骤:** - 1. 打开"控制面板" > "程序" > "启用或关闭 Windows 功能"。 - 2. 找到"NFS 服务",勾选所有子项(如"NFS 客户端"等)。 - 3. 点击"确定"保存设置,系统会自动安装所需组件。 - -### 2. 文件名乱码修正 - -- **问题描述:** 在挂载后,文件名可能显示为乱码,原因是 Windows 和飞牛 NAS 使用的字符编码不匹配。 - -- **解决方案:** - 1. 使用快捷键 `Win + R` 打开"运行"窗口,输入 `intl.cpl` 并回车。 - 2. 在"区域"设置中,切换到"管理"选项卡,点击"更改系统区域设置"。 - 3. 勾选"Beta 版:使用 Unicode UTF-8 提供全球语言支持"。 - 4. 点击"确定"保存设置,并重启计算机使配置生效。 - -### 其他注意事项 - -- 除了通过 Windows 自带的文件资源管理器添加网络位置外,用户还可以使用第三方工具(如 RaiDrive、WinSCP、FileZilla)来挂载 NFS 共享。 - ---- - -## SMB 3 多通道生效条件 - -多通道是 SMB 3 的一项功能,可以让多个有线网络分担流量来提高传输速度和增强可靠性。 - -若你已在飞牛 **系统设置** > **文件共享协议** > **SMB** > **高级设置** 中开启了 **SMB 3 多通道**,但速度上未有显著提升,请参考此文档检查以下条件是否满足: - -1. NAS 和客户端都具有多个网口,且 NAS 上实际连接的网口数量是客户端网口数量的整数倍; -2. NAS 与客户端网口可以互相通信(在同一子网或正确路由下); -3. NAS 与客户端所有网口都具有相同的速率(否则,当有不同速率的网口时,系统可能会优先选择最快的网口); -4. 客户端支持 SMB 3 多通道,包括 Windows 8、Windows Server 2012、macOS 10.13 (High Sierra) 及以上版本; -5. 硬盘及 CPU 性能良好,非性能瓶颈。 - ---- - -# 第九部分:虚拟机 - -## 如何安装和使用虚拟机? - -### 1. 安装应用 - -#### 1.1 应用中心安装虚拟机 - -- 使用管理员账号登录飞牛 fnOS。 -- 打开 **应用中心**,搜索并找到 **虚拟机** 应用。 -- 点击 **安装**。 - -#### 1.2 确认安装位置 - -- 虚拟机应用默认安装在系统分区。 -- 确认后点击 **下一步**。 - -#### 1.3 检查信息 - -- 点击 **确定** 开始安装。 - -#### 1.4 安装完成 - -- 安装完成后,桌面会出现 **虚拟机** 应用图标。 - -### 2. 使用准备 - -#### 2.1 网络设置 - -- 在 **系统设置 > 网络管理** 中,为有线网口或聚合网口启用 OVS(Open vSwitch),以提供虚拟网络支持(无线网口暂不支持)。 - -#### 2.2 驱动程序下载 - -- [VirtIO Guest Tool 驱动程序(适用于 Windows 7)](https://iso.liveupdate.fnnas.com/help_static/images/virtio-win-0.1.173.iso) -- [VirtIO Guest Tool 驱动程序(适用于 Windows 10 及以上)](https://iso.liveupdate.fnnas.com/help_static/images/virtio-win-0.1.266.iso) - -#### 2.3 开启 CPU 虚拟化支持 - -- 进入 BIOS/UEFI 设置(开机按 `Del`、`F2`、`F10` 或 `Esc`)。 -- 启用 **Intel Virtualization Technology**(Intel CPU)或 **SVM Mode**(AMD CPU)。 -- 保存设置并重启。 - -### 3. 创建虚拟机 - -#### Windows 系统创建步骤 - -1. 在 **虚拟机** 应用中点击 **创建虚拟机**。 -2. 输入虚拟机名称,选择操作系统类型和版本。 -3. 配置镜像和参数(如 ISO 文件、驱动文件、CPU 核心、内存等)。 -4. 添加虚拟磁盘(最多 8 个),选择总线类型(如 VirtIO)和存储空间。 -5. 添加虚拟网络(最多 8 个),选择 OVS 网口和网络类型。 -6. 点击 **创建**,完成后启动虚拟机并通过 VNC 连接。 - -#### Linux 系统创建步骤 - -1. 同上步骤创建虚拟机,选择 Linux 类型和版本。 -2. 配置镜像和参数(无需额外驱动,Linux 自带 VirtIO 驱动)。 -3. 添加虚拟磁盘和虚拟网络。 -4. 创建并启动虚拟机。 - -### 4. 操作系统安装流程 - -#### Windows 11 安装注意事项 - -- 选择 **Q35 + UEFI** 配置(支持 TPM 2.0)。 -- 在启动时按任意键进入安装流程。 -- 如使用 VirtIO 磁盘/网络,需加载对应驱动程序。 - -#### VirtIO 存储驱动程序安装 - -1. 在 Windows 安装界面点击"加载驱动程序"。 -2. 选择驱动 ISO 中的对应目录(如 `amd64/win11`)。 -3. 安装 **Red Hat VirtIO SCSI Controller**。 - ---- - -## 如何使用虚拟机硬件直通? - -硬件直通是一种虚拟化技术,它允许虚拟机直接访问宿主机上的物理硬件设备,而不是通过虚拟化层进行仿真。 - -### 应用场景 - -1. **显卡直通**:支持物理显示器输出,提升图形处理性能; -2. **声卡直通**:实现音响或耳机的高质量音频输出; -3. **USB设备直通**:便于读取USB存储设备或连接其他USB设备; -4. **网卡直通**:满足网络加速和复杂的家庭组网需求。 - -> **【硬件直通的风险和限制】** -> -> 硬件直通可能带来一定风险和限制,错误配置可能导致**设备冲突**或**系统崩溃**。在操作前,请充分了解以下风险: -> -> 1. **存储直通风险**:错误直通SATA、NVMe或USB存储设备可能损坏宿主机上的数据,且不可逆,请务必提前备份; -> 2. **网卡直通风险**:请确保宿主机保留可用网卡,否则可能导致宿主机网络中断; -> 3. **显卡直通风险**:直通显卡成功率并不高,且有可能造成宿主机崩溃,需谨慎操作。 -> -> 不建议非专业用户尝试硬件直通操作。 - -### 1. 开启硬件直通选项 - -打开虚拟机应用,在侧边栏点击 **硬件**,找到 **硬件直通选项** 的 **开启** 按钮,在仔细阅读 **《硬件直通技术免责声明》** 后, 点击 **确认开启**。 - -### 2. 虚拟机配置硬件直通 - -在创建虚拟机或编辑虚拟机界面, **添加设备** 按钮在 **硬件直通选项** 开启后便激活可点击。 - -#### 2.1 添加 USB 设备 - -1. 点击 **添加硬件** 按钮,点击 **添加 USB 设备** -2. 选择需要直通给当前虚拟机的 USB 设备 -3. 点击 **确定** 按钮保存 - -#### 2.2 添加 PCI 设备 - -1. 点击 **添加硬件** 按钮,点击 **添加 PCI 设备** -2. 在列表中选择需要直通的 PCI 设备 -3. 由于 PCI 设备根据 IOMMU 分组进行内存隔离,所以直通时需要将同一 IOMMU 分组的 PCI 设备一并直通 -4. 点击 **确定** 按钮保存 - -### 3. 虚拟机详细日志指引 - -由于多方面因素影响,虚拟机可能启动失败,可在日志中查询相关详细原因。 - -日志路径:`/var/apps/trim.vm/var/log/error.log` - ---- - -# 第十部分:飞牛同步 - -## 飞牛同步功能介绍 - -### 1. 下载对应的客户端 - -#### Windows - -1. 电脑端浏览器打开飞牛官网,选中 Windows 下载应用文件; -2. 安装飞牛同步应用。 - -#### Mac - -1. 电脑端浏览器打开飞牛官网,点击 Mac ,选择你的电脑对应的处理器版本下载应用文件; -2. 安装飞牛同步应用。 - -### 2. 连接 NAS - -#### 连接新设备 - -点击左下角"创建新任务"打开"连接新 NAS 设备"弹窗,你可通过以下多种访问方式连接到 NAS: - -1. **输入 IP 地址、域名或 FN ID** -2. **发现局域网内设备** - -> **注意:** -> - 出于数据安全性考量,飞牛同步当前仅支持 HTTPS; -> - HTTPS 访问 fnOS 默认端口为5667。 - -### 3. 登录授权 - -连接 NAS 设备成功后需进行账号授权。 - -> **注意:** -> - 授权后并不会将你的账号密码保存在客户端。 - -### 4. 创建任务 - -#### 4.1 设置同步规则 - -1. **同步模式**:目前支持3种同步模式: - - **双向同步**:NAS 端和电脑互为镜像,两端文件完全保持一致。 - - **仅下载**:仅同步 NAS 数据至电脑;NAS 文件删除时,电脑端文件将保留。 - - **仅上传**:仅同步电脑数据至 NAS;电脑端文件删除时,NAS 文件将保留。 - -2. **更多设置-设置过滤规则**: - 可以根据需要自行定义规则,排除指定大小或格式的文件。 - > **注意:** `tmp` 和 `temp` 为系统强制过滤的文件扩展名。 - -#### 4.2 设置同步路径 - -- **电脑文件路径**:选择电脑端上要同步的文件夹路径。 -- **NAS 文件路径**:选择 NAS 端上要同步的文件夹路径。 - -### 5. 查看任务 - -1. **主页面查看**:可通过任务卡片查看此任务的同步进度和同步状态。 -2. **系统托盘查看**:可通过系统任务栏中飞牛同步的icon快速打开托盘。 - -### 6. 系统设置 - -可通过「设置」功能对飞牛同步进行更新、限速等设置。 - ---- - -## 飞牛同步 - 常见使用问题说明 - -### 1. 新设备连接不上 - -- **原因**:飞牛同步默认使用HTTPS连接,需要正确输入HTTPS端口。 -- **解决方案**:如果使用IP连接且未修改端口,默认端口为5667。 - -### 2. 任务显示状态异常/账号异常 - -#### NAS 连接问题 - -- **可能原因**:NAS关闭、HTTPS端口被修改、账号密码变更等导致连接中断。 -- **解决方法**:通过"编辑"按钮更新NAS连接方式,重新授权。 - -#### 文件变更问题 - -- **可能原因**:文件被删除、移动或重命名导致同步异常。 -- **解决方法**:通过"恢复"按钮重启任务,重新同步文件。 - -### 3. 传输项目与文件数量对不上 - -- **原因1**:同步任务包含子文件夹,创建文件夹也会被统计为传输项目。 -- **原因2**:电脑端隐藏文件未被同步(需取消隐藏才能同步)。 - -### 4. 电脑端出现了冲突副本文件 - -- **常见场景:** - - 多用户同时编辑同一文件。 - - 文件被占用(编辑或预览)。 - - 未基于最新版本修改文件。 -- **表现**:冲突文件会被重命名(含用户名、"冲突副本"字样和时间戳),且不会上传至NAS。 - -### 5. 电脑端文件一直没有被同步 - -- **原因**:文件被其他应用程序占用,操作系统限制无法监听。 -- **解决方法**:退出占用文件的应用程序后,同步任务会自动继续。 - -### 6. 已创建的同步任务是否可以编辑 - -- **可编辑项**:同步模式、文件过滤规则、NAS连接方式。 -- **不可编辑项**:电脑端和NAS端的同步文件夹路径(无法修改)。 - ---- - -# 第十一部分:备份 - -## 加密备份功能介绍 - -### 1. 备份飞牛时开启加密 - -加密备份功能允许在将数据从飞牛NAS备份到百度网盘或阿里云盘时,对文件进行轻量化加密。 - -**操作步骤:** - -1. 进入"备份飞牛",选择目标网盘(百度网盘/阿里云盘)。 -2. 在第四步"备份规则"页面,启用"数据加密"并设置密码。 -3. 确认创建任务后即可开始加密备份。 - -**注意事项:** - -- 密码最长32位,区分英文大小写,支持特殊符号:`! @ # % & * ~`。 -- 加密后本地文件保持不加密状态。 -- 加密后的文件名会被混淆,扩展名变更为`.fot`。 -- 加密任务创建后无法取消数据加密设置,请妥善保管密码。 - -### 2. 备份网盘时开启解密 - -当从网盘恢复数据到飞牛NAS时,需启用解密功能。 - -**操作步骤:** - -1. 进入"备份远程存储",选择来源网盘。 -2. 在第四步"备份规则"页面,启用"数据解密"并输入密码。 -3. 确认创建任务后即可开始解密。 - -### 3. 常见问题 - -**Q1:加密后文件名会变吗?** - -A:会。加密后文件名会被混淆,后缀统一变为`.fot`。 - -**Q2:什么情况下备份至百度网盘的加密任务失败?** - -A:因百度网盘限制,需生成临时加密文件存放在本地后再上传。 - -**Q3:什么情况会解密失败?** - -A:常见原因: -1. 密码错误(注意英文大小写和特殊符号)。 -2. 配置解密任务后频繁修改网盘中加密文件夹。 - -**Q4:忘记密码怎么办?** - -A:为保障数据安全,密码以密文形式存储在本地,不会上报至飞牛服务器,因此无法找回。 - ---- - -# 第十二部分:iSCSI - -## iSCSI 名词介绍 - -### 1. iSCSI - -基于 IP 网络的存储协议,允许通过以太网远程访问和管理存储设备,数据通过网络传输到 NAS。 - -### 2. LUN(Logical Unit Number) - -代表可被定位的 SCSI 逻辑设备,相当于一块虚拟硬盘,可指定大小和存储空间,供客户端挂载和读写。 - -### 3. Thick 和 Thin 模式 - -- **Thick**:创建时一次性分配指定容量,性能稳定、延迟低。 -- **Thin**:创建时仅分配少量空间,根据数据写入动态扩展,存储空间利用率更高。 -- 两种模式的 LUN 大小均需至少 5 GB。 - -### 4. Target - -NAS 提供的访问地址,客户端通过 Target 连接 LUN,支持设置 CHAP 密码提升安全性。 - -### 5. IQN(iSCSI Qualified Name) - -用于唯一标识客户端和 Target 的名称,遵循特定命名格式,类似"身份证"。 - -### 6. Initiator(启动器) - -客户端上运行的程序,通过 IQN 向 Target 发起连接请求。 - -- Windows 系统自带 iSCSI Initiator -- macOS 需安装第三方工具(如 DAEMON Tools) - -### 7. 用户组 - -将不同设备的 Initiator IQN 分组,批量设置对 LUN 的读写权限。 - -### 8. CHAP(Challenge-Handshake Authentication Protocol) - -密码认证机制,防止未授权访问,支持两种模式: - -- **单向 CHAP**:仅 Initiator 验证 Target。 -- **相互 CHAP**:Initiator 和 Target 相互验证。 - ---- - -## iSCSI 基础功能介绍 - -### 1. 创建LUN - -- **前置条件**:建议在千兆及以上局域网环境中使用iSCSI。 -- **步骤**: - 1. 进入"应用-LUN",选择"新建LUN"。 - 2. 设置LUN所在的存储空间(支持Btrfs、EXT4文件系统)、容量(≥5GB,建议不超过2TB)及类型(推荐Thick Provisioning)。 - 3. 创建或选择已有的Target进行映射。 - 4. 确认后等待3-5秒完成创建。 - -### 2. 电脑端挂载 - -#### Windows系统 - -1. 按`Win+R`,输入`iscsicpl`打开iSCSI初始化器。 -2. 在"发现"选项卡中添加NAS的IP地址和端口(默认3260)。 -3. 在"目标"选项卡中选择对应的Target IQN并连接。 -4. 通过`diskmgmt.msc`打开磁盘管理,初始化磁盘(推荐GPT分区),格式化并分配盘符。 - -#### macOS系统 - -(需借助第三方工具,如DAEMON Tools) - -1. 打开DAEMON Tools,添加iSCSI设备(输入NAS IP和端口)。 -2. 选择Target并连接。 -3. 在"磁盘工具"中格式化LUN(可选APFS或exFAT文件系统)。 -4. 挂载后即可在Finder中访问。 - -### 注意事项 - -- 若连接失败,请检查IP/端口、网络连通性、iSCSI服务状态及防火墙设置。 -- MBR分区仅支持2TB以下容量,超过部分无法识别。 - ---- - -## iSCSI 用户组配置说明 - -本文介绍如何配置 iSCSI 用户组,包括如何获取启动器IQN以及设置用户组权限的方法。 - -### 获取启动器 IQN - -#### Windows 系统 - -1. 按`Win+R`,输入`iscsicpl`打开iSCSI初始化器。 -2. 在"目标"选项卡中点击"获取 IQN",即可复制当前的 IQN。 - -#### Mac 系统 - -1. 打开终端,输入命令获取 IQN。 -2. 复制显示的 IQN 字符串。 - -### 设置用户组权限 - -用户组可以将不同设备的 Initiator IQN 分组,批量设置对 LUN 的读写权限。 - -**操作步骤:** - -1. 进入"应用-用户组",点击"新建用户组"。 -2. 设置用户组名称。 -3. 将各设备的 IQN 添加到用户组中。 -4. 创建或编辑 LUN 时,选择该用户组即可批量设置权限。 - ---- - -# 第十三部分:AI - OpenClaw - -## 飞牛 OpenClaw 配置微信 ClawBot 教程 - -### 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - - 官方合作推荐:阿里云百炼,免费体验 7000 万 Token。 - -### 2. 微信 ClawBot 配置 - -#### 2.1 确认微信有 ClawBot 功能 - -微信正在逐步放开 ClawBot 功能,需要先升级微信到最新版本后,再确认当前的微信是否已有 ClawBot 功能。 - -1. 打开微信设置。 -2. 打开插件。 -3. 若看到微信 ClawBot 功能,则确定有 ClawBot 功能。 - -#### 2.2 微信扫码完成绑定 - -1. 回到飞牛 OpenClaw。 -2. 打开"消息渠道" -> "添加渠道"。 -3. 选择"微信" -> "生成二维码"。 -4. 使用手机微信扫描二维码绑定。 -5. 手机上确定连接。 -6. 重启 OpenClaw,用手机发送消息,有回复,即完成微信 ClawBot 配置。 - ---- - -## 飞牛 OpenClaw 配置钉钉机器人教程 - -### 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - - 官方合作推荐:阿里云百炼,免费体验 7000 万 Token。 - -### 2. 钉钉机器人配置 - -#### 2.1 打开钉钉开发者平台并创建应用 - -1. 登录[开发者后台](https://open-dev.dingtalk.com/)。 -2. 在**应用开发**下,点击**立即创建**,可一键创建 OpenClaw 机器人。 -3. 在创建 OpenClaw 界面,填写机器人基本信息。 -4. OpenClaw 创建成功后,会自动展示应用的 **Client ID** 和 **Client Secret**,请保存好用于后续使用。 - -#### 2.2 配置钉钉机器人消息渠道 - -1. 回到飞牛 OpenClaw。 -2. 打开"消息渠道" -> "添加渠道"。 -3. 选择"钉钉",输入 Client ID 和 Client Secret,并保存。 -4. 重启 OpenClaw,回到钉钉发送给机器人消息,有回复,即完成钉钉机器人配置。 - ---- - -## 飞牛 OpenClaw 配置飞书机器人教程 - -### 1. 前置准备 - -1. 在应用中心安装好飞牛 OpenClaw 应用和相关的依赖应用。 -2. 给 OpenClaw 配置 1 个模型。 - -### 2. 飞书机器人配置 - -#### 2.1 打开飞书开放平台并创建应用 - -1. 打开飞书开放平台:https://open.feishu.cn/app -2. 登录你的飞书账号 -3. 点击"创建企业自建应用" - -#### 2.2 获取 App ID 与 App Secret - -1. 创建完成后,进入应用详情页 -2. 记录 App ID 和 App Secret - -> ⚠️ **重要**:App Secret 务必保密,不要截图外传,不要发到群里。 - -#### 2.3 权限配置 - -点击左侧"权限管理",点击"批量导入权限",粘贴相关权限配置。 - -#### 2.4 启用机器人 - -1. 点击左侧"应用能力" -2. 找到"机器人"卡片,点击"启用" -3. 设置机器人名称 -4. 点击"保存" - -#### 2.5 发布版本 - -1. 点击左侧"版本管理与发布" -2. 点击"创建版本" -3. 填写版本信息 -4. 点击"保存" -5. 点击"申请发布" - -> 💡 **提示**:审批通过后,应用状态会变为"已发布"。这时候才能进行下一步。 - -#### 2.6 在飞牛 OpenClaw 中配置飞书机器人 - -1. 在消息渠道中配置"添加渠道",选择飞书,输入 App ID 与 App Secret,并保存 -2. 重启 OpenClaw 网关 - -#### 2.7 在飞书平台开启事件订阅 - -1. 回到飞书开放平台 -2. 点击左侧"事件与回调" -3. 在"事件订阅方式"中,选择"长连接"并保存 -4. 在"订阅事件"区域,点击"添加事件",搜索 `im.message.receive_v1` - -#### 2.8 完成配对并验证 - -1. 在飞书中找到你的机器人,进入私聊,发送任何消息,获得配对码 -2. 在 OpenClaw 中完成配对 -3. 只要机器人正常回复即可 - ---- - -# 第十四部分:安全性 - -## 安全披露 - -### 2026-02-24 应用安全更新公告 - -#### 青龙面板 - -**风险等级**:高危 - -**风险描述**:该应用存在安全缺陷,在端口公网暴露情况下,通过特定端口可能导致设备被恶意利用进行非法写入。 - -**影响范围**:已安装青龙面板(包括应用中心或 Docker 方式)并开启公网端口映射的实例。 - -**处置措施**: -- 已从应用中心下架,等待开发者更新; -- 立即停用该应用,或避免公网暴露 15700 端口。 - ---- - -### 2026-02-14 应用安全更新公告 - -#### Aria2 - -**风险等级**:高危 - -**风险描述**:该应用默认配置存在安全缺陷,在公网暴露情况下,通过特定方式可能导致设备被非法写入。 - -**影响范围**:所有已安装版本,开启公网的特定端口映射的实例。 - -**处置措施**: -- 已从应用中心下架,等待开发者更新; -- 立即停用该应用,选择替代应用或等待后续更新。 - -#### 道理鱼音乐 - -**风险等级**:中危 - -**风险描述**:旧版本存在 Docker 容器权限提升风险,导致该应用能够访问其容器限定范围之外的其他当前容器目录。 - -**影响范围**:0.1.9 及之前版本。 - -**处置措施**: -- 已发布修复版本 0.2.4; -- 前往应用中心尽快升级。 - -#### Office 预览 - -**风险等级**:低危 - -**风险描述**:在服务暴露于公网,且获取应用密钥情况下存在被碰撞文件风险隐患。 - -**影响范围**:0.8.7 及之前版本,且配置为公网访问的实例。 - -**处置措施**: -- 已发布修复版本 0.8.8; -- 前往应用中心尽快升级。 - ---- - -## 安全漏洞报告奖励计划 - -飞牛NAS(fnOS)官方发布的"安全漏洞报告奖励计划",旨在邀请安全研究人员、技术爱好者和开发者共同参与fnOS系统的安全共建。 - -### 1. 漏洞测试范围 - -**适用对象**: -- fnOS官方正式发布版本 -- 飞牛自有域名与服务 -- 官方App与API接口 - -**不包含**: -- 已停止维护的历史版本 -- 第三方插件或组件 -- 非官方修改固件 -- 非飞牛控制的第三方服务 - -### 2. 漏洞等级与奖励标准 - -| 等级 | 奖励范围 | -|------|----------| -| **严重漏洞(Critical)** | ¥20,000 – ¥50,000 | -| **高危漏洞(High)** | ¥5,000 – ¥20,000 | -| **中危漏洞(Medium)** | ¥1,000 – ¥5,000 | -| **低危漏洞(Low)** | ¥300 – ¥1,000 | - -### 3. 漏洞提交方式 - -- **邮箱**:safety@fnnas.com - -**提交内容需包含**: -- 漏洞详细描述及风险分析 -- 可复现的操作步骤 -- 受影响产品及fnOS版本号 -- 有效PoC或视频演示 -- 联系方式 - -### 4. 报告流程 - -1. **提交与评估**:48小时内初步评估。 -2. **验证与修复**:根据漏洞等级制定修复计划(严重漏洞10个工作日内修复)。 -3. **复测验证**:邀请提交者参与复测。 -4. **奖励发放**:复测通过后30天内发放奖励。 - -### 5. 威胁情报协作计划 - -- 鼓励提交与fnOS生态相关的威胁情报(如攻击源IP、恶意域名、攻击样本等)。 -- 高价值情报将根据其价值给予奖励或致谢。 - -### 6. 第三方应用安全说明 - -- 第三方应用的漏洞责任归开发者所有,飞牛不直接提供现金奖励。 -- 飞牛将协助建立沟通渠道,并视情况采取下架、公告等措施。 - -### 关键条款 - -- **负责任披露原则**:禁止在修复前公开漏洞细节,禁止利用漏洞进行破坏性行为。 -- **安全港条款**:遵循规则的研究人员不会被视为非法入侵。 -- **最终解释权**归飞牛所有。 - ---- - -# 第十五部分:更多 - -## 联系我们 - -### 官网链接 - -- 飞牛官网:https://www.fnnas.com - -### 公众号 - -- 关注飞牛官方公众号:"飞牛fnOS",了解最新消息。 - -### 商务合作 - -- 商务合作,请添加飞牛商务微信:fn_business - -### 功能问题/意见反馈 - -- 请填写[问题/意见反馈表单](https://trim-nas.feishu.cn/share/base/form/shrcn2iVepeqaASZzMF0iMNAC5e) -- 可加入飞牛公测反馈群,与飞牛团队面对面 - -### 应用生态 - -- 请求上架飞牛应用中心,请填写[申请表](https://trim-nas.feishu.cn/share/base/form/shrcn5m4MsyeqvEh6c4tm97pQKb),提交后,请静候。 - ---- - -## 本地账号与飞牛账号的区别? - -### 本地账号 - -- **用途**:用于访问本地网络中的飞牛 NAS 设备的用户验证。 -- **访问场景**: - - 飞牛 fnOS 登录页面; - - 飞牛 App 登录页面。 -- **如何创建**: - - 在系统安装及初始化时,创建管理员账号; - - 在 **系统设置** > **用户管理**,可创建普通用户账号。 -- **忘记密码**: - - 管理员:如果有其他管理员,可联系其他管理员进行重置密码。若无,请重置飞牛 fnOS。 - - 普通用户:请联系管理员进行重置密码。 - -### 飞牛账号 - -- **用途**:在使用飞牛云服务,比如 **FN Connect 远程服务** 时,飞牛 NAS 需要绑定有效的 **飞牛账号**。 -- **访问场景**:[飞牛账号](https://account.fnnas.com/login) 登录页面。 -- **如何创建**:填写手机号码,短信验证成功后,填写基础信息完成创建。 -- **忘记密码**:点击 [忘记密码](https://account.fnnas.com/forget),填写手机号,验证成功后可重置密码。 - ---- - -## 管理员和普通用户权限的区别? - -在 fnOS 上,用户分为 **管理员** 和 **普通用户** 两种角色。 - -### 超级管理员 - -**超级管理员** 特指在系统初始化时创建的第一个管理员账号,其用户 ID 为 1000。相较于其他管理员,超级管理员的特殊性仅体现在以下两点: - -- 创建后不能被删除,只能被停用。 -- 默认开启 SSH 权限(可在 **设置 > 用户管理 > 用户详情 > 更多** 中关闭)。 - -### 关键信息总结 - -| 角色类型 | 关键权限/特点 | -|----------|---------------| -| **管理员** | 拥有系统管理权限,可进行各项设置操作 | -| **普通用户** | 权限受限,仅能使用被授权的功能 | -| **超级管理员** | 初始化创建,UID 1000,不可删除,默认开启 SSH | - ---- - -*文档整理完成 | 来源:飞牛 fnOS 帮助中心 https://help.fnnas.com/* diff --git a/fnhelp/iscsi/iscsi-create.md b/fnhelp/iscsi/iscsi-create.md deleted file mode 100644 index a6f25af..0000000 --- a/fnhelp/iscsi/iscsi-create.md +++ /dev/null @@ -1,37 +0,0 @@ -# iSCSI 基础功能介绍 - -## 1. 创建LUN - -- **前置条件**:建议在千兆及以上局域网环境中使用iSCSI。 -- **步骤**: - 1. 进入"应用-LUN",选择"新建LUN"。 - 2. 设置LUN所在的存储空间(支持Btrfs、EXT4文件系统)、容量(≥5GB,建议不超过2TB)及类型(推荐Thick Provisioning)。 - 3. 创建或选择已有的Target进行映射。 - 4. 确认后等待3-5秒完成创建。 - -## 2. 电脑端挂载 - -### Windows系统 - -1. 按`Win+R`,输入`iscsicpl`打开iSCSI初始化器。 -2. 在"发现"选项卡中添加NAS的IP地址和端口(默认3260)。 -3. 在"目标"选项卡中选择对应的Target IQN并连接。 -4. 通过`diskmgmt.msc`打开磁盘管理,初始化磁盘(推荐GPT分区),格式化并分配盘符。 - -### macOS系统 - -(需借助第三方工具,如DAEMON Tools) - -1. 打开DAEMON Tools,添加iSCSI设备(输入NAS IP和端口)。 -2. 选择Target并连接。 -3. 在"磁盘工具"中格式化LUN(可选APFS或exFAT文件系统)。 -4. 挂载后即可在Finder中访问。 - -## 注意事项 - -- 若连接失败,请检查IP/端口、网络连通性、iSCSI服务状态及防火墙设置。 -- MBR分区仅支持2TB以下容量,超过部分无法识别。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/iscsi/iscsi-definition.md b/fnhelp/iscsi/iscsi-definition.md deleted file mode 100644 index 88c5155..0000000 --- a/fnhelp/iscsi/iscsi-definition.md +++ /dev/null @@ -1,45 +0,0 @@ -# iSCSI 名词介绍 - -## 1. iSCSI - -基于 IP 网络的存储协议,允许通过以太网远程访问和管理存储设备,数据通过网络传输到 NAS。 - -## 2. LUN(Logical Unit Number) - -代表可被定位的 SCSI 逻辑设备,相当于一块虚拟硬盘,可指定大小和存储空间,供客户端挂载和读写。 - -## 3. Thick 和 Thin 模式 - -- **Thick**:创建时一次性分配指定容量,性能稳定、延迟低。 -- **Thin**:创建时仅分配少量空间,根据数据写入动态扩展,存储空间利用率更高。 -- 两种模式的 LUN 大小均需至少 5 GB。 - -## 4. Target - -NAS 提供的访问地址,客户端通过 Target 连接 LUN,支持设置 CHAP 密码提升安全性。 - -## 5. IQN(iSCSI Qualified Name) - -用于唯一标识客户端和 Target 的名称,遵循特定命名格式,类似"身份证"。 - -## 6. Initiator(启动器) - -客户端上运行的程序,通过 IQN 向 Target 发起连接请求。 - -- Windows 系统自带 iSCSI Initiator -- macOS 需安装第三方工具(如 DAEMON Tools) - -## 7. 用户组 - -将不同设备的 Initiator IQN 分组,批量设置对 LUN 的读写权限。 - -## 8. CHAP(Challenge-Handshake Authentication Protocol) - -密码认证机制,防止未授权访问,支持两种模式: - -- **单向 CHAP**:仅 Initiator 验证 Target。 -- **相互 CHAP**:Initiator 和 Target 相互验证。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/iscsi/iscsi-group.md b/fnhelp/iscsi/iscsi-group.md deleted file mode 100644 index 2efaeee..0000000 --- a/fnhelp/iscsi/iscsi-group.md +++ /dev/null @@ -1,30 +0,0 @@ -# iSCSI 用户组配置说明 - -本文介绍如何配置 iSCSI 用户组,包括如何获取启动器IQN以及设置用户组权限的方法。 - -## 获取启动器 IQN - -### Windows 系统 - -1. 按`Win+R`,输入`iscsicpl`打开iSCSI初始化器。 -2. 在"目标"选项卡中点击"获取 IQN",即可复制当前的 IQN。 - -### Mac 系统 - -1. 打开终端,输入命令获取 IQN。 -2. 复制显示的 IQN 字符串。 - -## 设置用户组权限 - -用户组可以将不同设备的 Initiator IQN 分组,批量设置对 LUN 的读写权限。 - -**操作步骤:** - -1. 进入"应用-用户组",点击"新建用户组"。 -2. 设置用户组名称。 -3. 将各设备的 IQN 添加到用户组中。 -4. 创建或编辑 LUN 时,选择该用户组即可批量设置权限。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/media/atv-infuse.md b/fnhelp/media/atv-infuse.md deleted file mode 100644 index 21db46a..0000000 --- a/fnhelp/media/atv-infuse.md +++ /dev/null @@ -1,68 +0,0 @@ -# Apple TV 上如何通过 Infuse 使用飞牛影视? - -[Infuse](https://firecore.com/infuse) 是 Apple 生态下的一款播放器,适用于 iPhone、iPad、Apple TV 和 Mac 。由于飞牛影视暂未发布 Apple TV 版本,有需要的用户可在 Apple TV 上自行安装 Infuse ,并按照本文指引连接飞牛影视,达到以下使用效果: - -- 从 Infuse 上查看飞牛影视上的媒体库,显示由飞牛刮削的影片信息 -- 播放影片,支持切换音频,支持切换内置、外挂字幕,播放记录与飞牛客户端同步 -- 通过搜索、按类型筛选、排序等方式查找影片 - -> **注意:** -> - 此功能需将飞牛影视应用升级至 V0.8.17 及以上版本,Apple tvOS 和 iOS 需升级到18 版本,Infuse 建议升级到最新版本。 -> - 飞牛影视对 Infuse 的主要功能做了兼容处理。由于二者在功能上存在客观差异,在 Infuse 上使用与在飞牛上使用的体验将略有差别。 - -## 1. Infuse 连接飞牛影视服务器 - -### TV - 方式 1:局域网内扫描发现(Apple TV) - -1. 打开 Infuse,点击右上角 **设置** ,点击 **共享** -2. 点击 **添加媒体服务器** -3. 在 **可用服务器** 中找到带有"飞牛影视"前缀的服务器,点击选择 -4. 输入飞牛影视的用户名和密码 -5. (可选)点击 **高级设置** > **媒体库模式** 开关,打开后 Infuse 首页将按照 Infuse 方式聚合展示飞牛上的影片 -6. 点击 **保存** 确认连接,连接成功后首页将出现飞牛影视服务器上的媒体库 - -### TV - 方式 2:手动连接(Apple TV) - -1. 打开 Infuse,点击右上角 **设置** ,点击 **共享** -2. 点击 **添加媒体服务器** -3. 在 **可用服务器** 中选择 **其他 Jellyfin** -4. 输入飞牛影视服务器的 IP、用户名和密码,(可选)将名称修改为便于识别的名称 -5. 点击 **高级设置** , **端口** 输入 8005 -6. (可选)**媒体库模式** 开关,打开后 Infuse 首页将按照 Infuse 方式聚合展示飞牛上的影片 -7. 点击 **新增** 确认连接,连接成功后首页将出现飞牛影视服务器上的媒体库 - -### App - 方式 1:局域网内扫描发现(iPad / iPhone) - -1. 打开 Infuse,点击右下角 **设置** ,点击 **新增文件来源** -2. 在 **可用的共享** 中找到带有"飞牛影视"前缀的服务器,点击选择 -3. 输入飞牛影视的用户名和密码 -4. (可选)点击 **高级设置** > **媒体库模式** 开关 -5. 点击 **新增** 确认连接 - -> **注意:** -> - 当局域网内有其他 Emby 或 Jellyfin 服务器时,将可能无法扫描到飞牛影视服务器。请改用手动连接方式。 -> - 如果飞牛设备有多个网卡,请检查扫描到的服务器 IP 是否符合预期。如果不是,请改用手动连接方式连接目标 IP 。 - -### App - 方式 2:手动连接(iPad / iPhone) - -1. 打开 Infuse,点击右下角 **设置** ,点击 **新增文件来源** -2. 共享列表滚动到底部,点击 **媒体服务器** > **其他 Jellyfin** -3. 输入飞牛影视服务器的 IP、用户名和密码 -4. 点击 **高级设置** , **端口** 输入 8005 -5. (可选)**媒体库模式** 开关 -6. 点击 **新增** 确认连接 - -## 2. 飞牛影视与 Infuse 体验差异 - -在 Infuse 上使用飞牛影视,相较于直接使用飞牛影视客户端: - -- 同个影片或同一集的多个版本不会聚合,将平铺展示(在 **高级** 中打开 **媒体库模式** ,则从首页访问时会聚合) - -相较于在 Infuse 上直接挂载影视目录或使用其他服务器: - -- 部分排序方式不支持,如按文件夹、按播放次数等 -- 不支持安装 InfuseSync 插件 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/media/create-users.md b/fnhelp/media/create-users.md deleted file mode 100644 index 528b9db..0000000 --- a/fnhelp/media/create-users.md +++ /dev/null @@ -1,48 +0,0 @@ -# 如何为家人朋友开通影视账号? - -## 1. 影视独立账号 - -### 1.1 如何开通? - -- 使用影视管理员账号登录影视应用。 -- 点击首页右上角 → 设置 → 影视服务器 - 用户 → 新增用户。 -- 设置用户名、密码,并选择可访问的媒体库范围。 -- 点击确认保存。 - -### 1.2 如何登录? - -- **网页端**:在浏览器中输入NAS地址后加 `/v`,使用影视独立账号登录。 -- **App端**:目前不支持独立账号登录,后续计划推出独立影视App。 -- **TV端**:参考安装指南。 - -## 2. NAS账号登录 - -### 2.1 哪些NAS用户可以访问影视? - -- 默认所有NAS用户均可访问影视应用。 -- 可修改为仅管理员可见: - 1. 使用NAS管理员账号进入设置 → 应用 → 影视。 - 2. 将"哪些用户可以从桌面访问"设置为"仅管理员"。 - 3. 点击确定保存(注:此设置仅限制桌面图标显示,不影响通过浏览器直接访问)。 - -### 2.2 如何用NAS账号登录? - -- **网页端/TV端**:在影视登录页选择"使用NAS账号登录",跳转至NAS授权页面确认。 -- **App端**:同网页端流程。 - -### 2.3 用NAS账号登录的用户可访问哪些媒体库? - -- 新用户默认无媒体库访问权限,需由影视管理员设置: - 1. 使用影视管理员账号进入设置 → 影视服务器 - 用户 → 新用户默认权限设置。 - 2. 选择可访问的媒体库范围并保存。 -- 此设置影响后续所有NAS新用户的默认权限,支持按需个性化调整。 - -## 关键说明 - -- 影视账号与NAS账号为两套独立体系,可根据需求选择开通类型。 -- 影视独立账号适用于仅需影视服务的用户;NAS账号适用于需使用全功能(如文件存储、相册等)的用户。 -- 权限管理灵活,支持按用户或媒体库细分访问范围。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/media/media-install.md b/fnhelp/media/media-install.md deleted file mode 100644 index 9e69f4d..0000000 --- a/fnhelp/media/media-install.md +++ /dev/null @@ -1,76 +0,0 @@ -# 如何安装飞牛影视和创建媒体库? - -## 1. 安装飞牛影视 - -### 步骤1:通过应用中心安装 - -使用管理员账号登录网页端,打开"应用中心",找到"影视"应用并点击安装。 - -### 步骤2:确认安装位置 - -默认安装在系统分区,需指定媒体元数据存储位置(后续可修改)。 - -### 步骤3:检查信息并确认安装 - -### 步骤4:安装完成 - -桌面图标更新为已安装状态。 - -## 2. 设置影视可访问的文件范围 - -**入口**:通过应用中心或系统设置中的"影视"应用管理。 - -**添加文件夹**:可选择"我的文件""存储空间""外接存储""远程挂载"等路径,并设置为读写权限。 - -**保存设置**:点击"确定"保存,若路径异常需参考相关问题处理。 - -## 3. 影视初始化 - -### 3.1 打开方式 - -通过桌面图标、App或浏览器直接访问(NAS地址后加`/v`)。 - -### 3.2 授权登录 - -使用NAS管理员账号授权登录。 - -### 3.3 创建影视管理员账号 - -设置独立的影视管理员用户名和密码(与NAS账号体系独立)。 - -### 3.4 服务器设置 - -- 设置服务器名称 -- 首选语言 -- 元数据存储位置 -- 实时监控文件更新等 - -## 4. 创建媒体库 - -### 4.1 基础信息 - -选择内容类型(如电影、电视剧等),创建后不可修改。 - -### 4.2 媒体文件夹 - -添加已授权访问的文件夹路径。 - -### 4.3 高级设置 - -- 元数据下载语言(中文/英文) -- 是否允许成人内容(基于TMDB标签) -- 自动下载字幕及字幕语言设置 - -### 4.4 创建成功 - -系统自动开始扫描文件、刮削元数据、下载字幕等。 - -## 关键注意事项 - -- 影视应用的用户权限与NAS权限独立,管理员需单独设置文件访问范围。 -- 朋友仅需影视账号,家人可同时使用NAS账号登录影视。 -- 成人内容过滤依赖TMDB标签,部分未标记内容可能无法完全屏蔽。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/photo/photo-ai.md b/fnhelp/photo/photo-ai.md deleted file mode 100644 index 54eee0d..0000000 --- a/fnhelp/photo/photo-ai.md +++ /dev/null @@ -1,58 +0,0 @@ -# AI 相册功能说明 - -## 一、安装说明 - -### 1.1 硬件配置要求 - -- **内存**:最低 2 GB(智能识别增强模型需 8 GB) -- **存储空间**:模型安装位置需 ≥20 GB - -### 1.2 下载安装模型 - -- **人脸识别**:仅支持"标准模型" -- **智能识别**:可选"基础模型"(内存≥2GB)或"增强模型"(内存≥8GB) - -### 1.3 GPU 设置 - -- **支持显卡**:Intel GPU、NVIDIA(GeForce 700以上) -- **常见问题**:驱动缺失、显存不足、GPU繁忙或老旧显卡不兼容 - -## 二、任务执行 - -- **触发方式**:安装模型后自动执行、上传文件后自动触发、手动执行未识别内容、重置识别任务 -- **任务失败处理**:检查GPU兼容性、存储状态、文件权限,或等待系统资源空闲后重试 - -## 三、AI 相册功能详解 - -### 3.1 人脸识别高级设置 - -- **置信度阈值**:影响人脸检测准确度(不建议修改) -- **最少照片数**:控制生成人物相册的最低照片数量 -- **相似度差异阈值**:调整人物聚合的精确度(可选"宽松聚合") - -### 3.2 人物管理 - -支持命名、合并人物、隐藏/显示人物、手动添加面孔。 - -### 3.3 AI 搜索 - -支持自然语言搜索(如"在海边散步""吃披萨")。开启"视频增强识别"可提升视频搜索效果。 - -### 3.4 智能分类 - -自动根据照片/视频内容生成分类标签(如地点、活动)。 - -### 3.5 视频增强识别 - -- 仅支持 **.mp4、.mov** 格式 -- 占用资源较多,建议在完成基础识别后单独启用 - -## 注意事项 - -- 模型文件需通过官方渠道下载,避免安装失败 -- 任务执行时避免同时运行其他高负载应用 -- 视频增强识别耗时较长,需耐心等待 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/photo/photo-backup.md b/fnhelp/photo/photo-backup.md deleted file mode 100644 index a0e62f2..0000000 --- a/fnhelp/photo/photo-backup.md +++ /dev/null @@ -1,69 +0,0 @@ -# 如何备份手机照片? - -通过飞牛 App 的 **相册备份** 功能,您可以将手机本地的照片备份上传到飞牛 NAS 上,以节省手机空间。 - -> **前置准备:** -> -> 1. 确保已经安装 **相册** 应用; -> 2. 确保 **备份位置** 的存储空间足够,如果不足,请 **修改备份位置**; -> 3. 下载安装飞牛 App,连接到飞牛 NAS; -> 4. 手机设备需要给飞牛 App 开启本地照片或文件的访问权限。 - -## 操作步骤 - -1. 打开飞牛 App → **相册** → **设置** → **备份设置**; -2. 开启 **相册自动备份** 功能开关。开启备份后,可查看到 **备份状态**: - -> **备份状态说明:** -> -> 1. **扫描中**:正在扫描手机设备本地的照片和视频数量以及大小,准备备份上传。 -> 2. **正在备份**:正在备份上传,会显示剩余数量以及已完成备份上传的数量。 -> 3. **备份成功**:该次备份已完成,全部成功或部分失败。如果部分失败,点击 **备份状态栏** 可跳转至 **任务传输** 查看详细错误信息。 -> 4. **备份暂停或失败**:可能存在以下原因: -> - **电量不足**:如果有开启 **节能备份**,当手机设备电量低于 15% 时,将暂停备份。 -> - **没有开启 Wi-Fi**:如果没有开启 **使用流量备份**,在非 Wi-Fi 环境下将会暂停备份。 -> - **连接断开**:如果网络环境变换,导致与飞牛 NAS 的连接断开,备份将暂停。 -> - **存储空间不足**:如果 **备份位置** 的存储空间不足。 -> - **存储空间无权限**:请联系管理员处理。 - -3. 如果在备份的过程中关闭备份,将取消正在备份以及未备份的队列任务,但已完成备份的照片及视频将保留。 - -## 备份规则 - -- **备份所有**:将扫描和备份设备中的所有照片和视频,将跳过已备份的照片,不会重复备份。 -- **备份新照片**:仅备份从现在起新拍摄的照片和视频。 - -**继续备份** 将在上一次备份规则的基础上继续备份。 - -## 备份目录偏好 - -### 苹果设备 - -- **按拍摄日期**:按照片的拍摄日期,创建并保存到子文件夹。"备份位置/年/月/" -- **保存在一个文件夹下**:所有照片都保存在一个文件夹下。 - -### 安卓设备 - -- **默认**:区分照片的目录,DCIM 目录下的照片按拍摄日期保存。 -- **按拍摄日期**:按照片的拍摄日期保存。 -- **按设备目录**:按当前设备的文件夹目录结构直接保存。 -- **保存在一个文件夹下**:所有照片都保存在一个文件夹下。 - -## 不熄屏备份 - -开启备份之后,若需要备份大量照片和视频,可开启 **不熄屏备份**,允许飞牛相册在屏幕全暗的状态下持续备份上传直至完成。 - -> 若要保持 **不熄屏备份**,请注意: -> -> - 保持稳定的网络连接; -> - 保持设备在充电状态; -> - 请勿关闭手机屏幕或退出 App。 - -## 其他备份设置 - -- **按拍摄日期重命名**:上传时按照"IMG_年月日_时分秒"的格式重命名文件。 -- **同步创建相册**:上传、备份时,根据已有的手机相册同步在飞牛相册应用中创建相册。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/photo/photo-share.md b/fnhelp/photo/photo-share.md deleted file mode 100644 index 90ce8eb..0000000 --- a/fnhelp/photo/photo-share.md +++ /dev/null @@ -1,39 +0,0 @@ -# 如何共享相册给设备内其他用户? - -你可以在相册应用中通过 **共享相册** 功能将相册共享给设备内的其他用户,可以设置公开给设备内所有用户或者仅共享给指定用户。 - -## 1. 管理员创建用户 - -使用管理员账号,在 **设置** > **用户管理** > **新增用户** 可以创建用户或者用户组。 - -## 2. 共享相册 - -你可以通过 2 种方式,将相册共享给设备内的其他用户。 - -### Web 端操作 - -**1. 共享相册(Web)** - -在相册详情中,点击 **共享** 可设置将相册 **共享给此设备的所有账号** 或者 **共享给指定账号**。 - -> 注意:相册共享可选择设置 2 种权限 -> - **可查看** 权限:被共享者可查看相册、预览内容详细、可下载内容。 -> - **可添加** 权限:被共享者在拥有 **可查看** 权限基础上,可添加内容进入相册、可从相册中移除内容。 - -**2. 共享照片将自动创建共享相册(Web)** - -在 **图库**、**相册详情**、**预览详情** 或 **搜索结果** 中,选中多个或者单个照片进行 **共享**。此方式共享,会以当前日期命名创建 **共享相册**,并且显示在 **我的共享** 中。 - -### App 端操作 - -**1. 共享相册(App)** - -在相册详情中,点击 **共享** 可设置将相册 **共享给此设备的所有账号** 或者 **共享给指定账号**。 - -**2. 共享照片将自动创建共享相册(App)** - -在 **图库**、**相册详情**、**预览详情** 或 **搜索结果** 中,选中多个或者单个照片进行 **共享**。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/safety/safe-report.md b/fnhelp/safety/safe-report.md deleted file mode 100644 index a20db16..0000000 --- a/fnhelp/safety/safe-report.md +++ /dev/null @@ -1,63 +0,0 @@ -# 安全漏洞报告奖励计划 - -飞牛NAS(fnOS)官方发布的"安全漏洞报告奖励计划",旨在邀请安全研究人员、技术爱好者和开发者共同参与fnOS系统的安全共建。 - -## 1. 漏洞测试范围 - -**适用对象**: -- fnOS官方正式发布版本 -- 飞牛自有域名与服务 -- 官方App与API接口 - -**不包含**: -- 已停止维护的历史版本 -- 第三方插件或组件 -- 非官方修改固件 -- 非飞牛控制的第三方服务 - -## 2. 漏洞等级与奖励标准 - -| 等级 | 奖励范围 | -|------|----------| -| **严重漏洞(Critical)** | ¥20,000 – ¥50,000 | -| **高危漏洞(High)** | ¥5,000 – ¥20,000 | -| **中危漏洞(Medium)** | ¥1,000 – ¥5,000 | -| **低危漏洞(Low)** | ¥300 – ¥1,000 | - -## 3. 漏洞提交方式 - -- **邮箱**:safety@fnnas.com - -**提交内容需包含**: -- 漏洞详细描述及风险分析 -- 可复现的操作步骤 -- 受影响产品及fnOS版本号 -- 有效PoC或视频演示 -- 联系方式 - -## 4. 报告流程 - -1. **提交与评估**:48小时内初步评估。 -2. **验证与修复**:根据漏洞等级制定修复计划(严重漏洞10个工作日内修复)。 -3. **复测验证**:邀请提交者参与复测。 -4. **奖励发放**:复测通过后30天内发放奖励。 - -## 5. 威胁情报协作计划 - -- 鼓励提交与fnOS生态相关的威胁情报(如攻击源IP、恶意域名、攻击样本等)。 -- 高价值情报将根据其价值给予奖励或致谢。 - -## 6. 第三方应用安全说明 - -- 第三方应用的漏洞责任归开发者所有,飞牛不直接提供现金奖励。 -- 飞牛将协助建立沟通渠道,并视情况采取下架、公告等措施。 - -## 关键条款 - -- **负责任披露原则**:禁止在修复前公开漏洞细节,禁止利用漏洞进行破坏性行为。 -- **安全港条款**:遵循规则的研究人员不会被视为非法入侵。 -- **最终解释权**归飞牛所有。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/safety/vulnerability-report.md b/fnhelp/safety/vulnerability-report.md deleted file mode 100644 index a429628..0000000 --- a/fnhelp/safety/vulnerability-report.md +++ /dev/null @@ -1,59 +0,0 @@ -# 安全披露 - -## 2026-02-24 应用安全更新公告 - -### 青龙面板 - -**风险等级**:高危 - -**风险描述**:该应用存在安全缺陷,在端口公网暴露情况下,通过特定端口可能导致设备被恶意利用进行非法写入。 - -**影响范围**:已安装青龙面板(包括应用中心或 Docker 方式)并开启公网端口映射的实例。 - -**处置措施**: -- 已从应用中心下架,等待开发者更新; -- 立即停用该应用,或避免公网暴露 15700 端口。 - ---- - -## 2026-02-14 应用安全更新公告 - -### Aria2 - -**风险等级**:高危 - -**风险描述**:该应用默认配置存在安全缺陷,在公网暴露情况下,通过特定方式可能导致设备被非法写入。 - -**影响范围**:所有已安装版本,开启公网的特定端口映射的实例。 - -**处置措施**: -- 已从应用中心下架,等待开发者更新; -- 立即停用该应用,选择替代应用或等待后续更新。 - -### 道理鱼音乐 - -**风险等级**:中危 - -**风险描述**:旧版本存在 Docker 容器权限提升风险,导致该应用能够访问其容器限定范围之外的其他当前容器目录。 - -**影响范围**:0.1.9 及之前版本。 - -**处置措施**: -- 已发布修复版本 0.2.4; -- 前往应用中心尽快升级。 - -### Office 预览 - -**风险等级**:低危 - -**风险描述**:在服务暴露于公网,且获取应用密钥情况下存在被碰撞文件风险隐患。 - -**影响范围**:0.8.7 及之前版本,且配置为公网访问的实例。 - -**处置措施**: -- 已发布修复版本 0.8.8; -- 前往应用中心尽快升级。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/settings/create-users.md b/fnhelp/settings/create-users.md deleted file mode 100644 index 30a42a7..0000000 --- a/fnhelp/settings/create-users.md +++ /dev/null @@ -1,32 +0,0 @@ -# 如何创建新用户? - -在 fnOS 上,你可以创建多个用户账号,方便多人共同使用 NAS。 - -## 1. 设置入口 - -使用管理员账号,打开 **设置 > 用户管理 > 用户**,点击 **新增用户**。 - -## 2. 账号信息 - -- **用户类型**:区分管理员和普通用户 2 种角色权限。 -- **用户名**:支持中文、英文、数字,不能包含特殊符号和空格。 -- **备注**:可选填写。 -- **密码**:不少于 6 位,至少包含大写字母、小写字母、数字、符号中的 2 种。点击 **使用随机密码**,将填入随机密码并自动复制到剪贴板。 - -## 3. 加入用户组 - -可选择是否要将新用户加入到已有的用户组中。如需创建用户组,请前往 **设置 > 用户管理 > 用户组 > 新增用户组**。 - -## 4. 设置可用存储空间及配额 - -你可以按单个用户设置允许其使用哪个存储空间,不希望该用户使用的存储空间,可点击 **×** 按钮移除。管理员用户允许使用系统上的所有存储空间。 - -每个存储空间下该用户可使用的容量默认无限制,可手动输入数值修改。 - -## 5. 创建完成 - -后续可在用户详情中查看该用户的存储空间用量,进行修改、停用、删除等操作。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/settings/port-customization.md b/fnhelp/settings/port-customization.md deleted file mode 100644 index 000c01a..0000000 --- a/fnhelp/settings/port-customization.md +++ /dev/null @@ -1,47 +0,0 @@ -# 如何修改飞牛系统的端口? - -fnOS 版本更新到 V0.8.22 版本之后,在 **设置** 中可以修改飞牛NAS的 **HTTP 和 HTTPS 端口**、设置 **强制 HTTPS 连接**、设置是否固定占用并 **重定向 80 与 443 端口**。 - -## 1. 默认端口 - -fnOS 从 V0.8.22 版本之后,默认端口修改为 **HTTP 5666** 和 **HTTPS 5667** 端口。 - -> **注意:** -> - 在公测阶段飞牛仍继续占用 8000 和 8001 端口,允许用户通过 8000 和 8001 端口访问到飞牛系统。 -> - 在正式版本之后将不再使用 8000 和 8001 端口。 - -## 2. 如何使用不同端口号访问 fnOS? - -### 网页 - -在网页浏览器的地址栏中输入 `http://192.168.1.10:5666`(IP 地址例子) - -> **注意:** -> - 如果已开启了 **重定向 80 与 443 端口**,则可以不需要填写端口号。 - -### App - -在访问方式中输入 `http://192.168.1.10:5666`(IP 地址例子) - -> **注意:** -> - **如果不填写端口,App 和 TV 会默认跳转 HTTP 8000 和 HTTPS 8001 端口**。 - -## 3. 如何修改端口 - -系统升级到 V0.8.22 版本之后,在 **设置 - 安全性** 中可以设置端口。 - -> **注意:** -> - HTTP 和 HTTPS 端口号不可为空,部分端口号不可用。 -> - 确认修改端口,将会导致正在上传的任务或者正在进行的服务中断。 - -### 强制 HTTPS 连接 - -启用了**强制 HTTPS 连接**之后,系统将 HTTP 连接重定向到 HTTPS 连接。HTTPS 连接,浏览器将会验证安全、有效的证书才能正常访问。 - -### 重定向 80 与 443 端口 - -系统将默认启用 80 与 443 端口。启用之后,若你访问 80 或 443 端口,系统将 80 与 443 端口分别重定向到飞牛系统的 HTTP 端口 和 HTTPS 端口。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/settings/wifi.md b/fnhelp/settings/wifi.md deleted file mode 100644 index 2346833..0000000 --- a/fnhelp/settings/wifi.md +++ /dev/null @@ -1,27 +0,0 @@ -# 如何使用无线网卡? - -fnOS 支持无线网卡,你可通过《 fnOS 支持网卡范围验证表》查看目前已验证兼容的网卡型号。 - -## 1. 启用无线网卡 - -使用管理员账号,打开 **系统设置** > **网络设置**,可以看到已接入的无线网卡。点击 **更多** > **启用无线网卡**。 - -## 2. 连接网络 - -启用后将自动扫描局域网内的网络。点击要连接的网络,输入密码,点击确定。 - -如果网络未被扫描发现,或为隐藏网络,你可以点击 **手动添加**,输入网络名称和密码来连接。 - -## 3. 切换网络 - -选择无线网卡,点击 **更多** > **编辑**,打开网络列表,点击或手动添加要切换的网络。 - -> **注意:** -> 切换网络后IP将发生变化,可能会导致当前界面连接断开。建议: -> - 若当前有其他已连接的网络,请记住其IP,在切换网络后通过该 IP 访问; -> - 若已开启 FNConnect 服务,在切换网络后可通过 FNID 访问; -> - 若你当前和设备位于同个局域网,在切换网络后可通过飞牛 App 扫描局域网内的设备以获取新 IP。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/start/install-app.md b/fnhelp/start/install-app.md deleted file mode 100644 index bd18c41..0000000 --- a/fnhelp/start/install-app.md +++ /dev/null @@ -1,56 +0,0 @@ -# 如何安装 App 并连接到飞牛 NAS? - -本文将介绍如何安装飞牛 App 并且连接到飞牛 NAS。 - -## 1. 下载并安装 APP - -### iOS 设备 - -1. 打开 AppStore 搜索"飞牛",找到飞牛 App; -2. 下载并安装飞牛 App。 - -### Android 设备 - -1. 手机浏览器打开飞牛官网,点击"下载 App"开始下载 APK 应用文件; -2. 安装飞牛 App。 - -## 2. 连接到飞牛 NAS - -### 2.1 同意隐私协议 - -请同意隐私协议以使用飞牛 App。 - -### 2.2 选择访问方式 - -你可通过以下多种访问方式连接到 NAS: - -**输入 IP 地址、域名或 FN ID** - -- **IP 地址**:安装并且完成初始化系统后,可通过路由器管理中找到飞牛 NAS 的 IP 地址。 -- **DDNS 域名**:如果你在 fnOS 或路由器中配置了 DDNS 域名,可填写域名链接。 -- **FN ID**:如果你在 fnOS 中开启了 FN Connect 远程访问服务,可输入 FN ID 访问到飞牛 NAS,成功登录后,FN Connect 会自动安全地访问到飞牛 NAS。 - -> **注意:** -> - HTTP 访问 fnOS 默认端口为 5666(V0.8.22 版本改为 5666,依旧支持 8000); -> - HTTPS 访问 fnOS 默认端口为 5667(V0.8.22 版本改为 5667,依旧支持 8001); -> - App 登录会根据 HTTPS 开关使用默认端口,如果你在路由器中修改了端口设置,需要在 IP 地址后填写端口以正确访问到飞牛 NAS,比如 `192.168.41.459:端口号`; -> - FN ID 无需填写端口号。 - -**发现局域网内设备** - -- 进入 App 后,点击"发现局域网内设备"可进入查找列表,会自动发现并展示设备同局域网下的飞牛 NAS 设备,选择要连接的 NAS 设备; - -> **注意:** -> - 使用"发现局域网内设备"功能需要开启设备的本地网络访问权限。 - -**历史登录** - -- 如果该设备有历史登录记录,可通过"历史登录"快速填充访问信息及账号信息。 - -### 2.3 输入登录信息 - -输入飞牛 NAS 的登录账号和密码。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/start/install-os.md b/fnhelp/start/install-os.md deleted file mode 100644 index aba7e17..0000000 --- a/fnhelp/start/install-os.md +++ /dev/null @@ -1,93 +0,0 @@ -# 如何安装和初始化飞牛 fnOS? - -你可以将 fnOS 安装部署到你的设备上,我们支持主流的 x86 架构硬件。以下是安装步骤: - -## 1. 下载安装镜像 - -前往 [飞牛官网](https://www.fnnas.com/download?key=fnos) 下载最新的系统安装 iso 镜像。 - -## 2. 制作引导盘 - -此步骤需要在电脑上使用写盘工具将镜像文件烧录到 U盘中。U 盘在完成安装后就可推出。写盘工具可选择 Rufus ([Rufus4.5 下载 👍](https://static2.fnnas.com/installer/rufus-4.5.exe)、[Rufus 4.0 下载](https://static2.fnnas.com/installer/rufus-4.0.exe)、[Rufus 官网](https://rufus.ie/))、[Balenaetcher](https://etcher.balena.io/) 等。下文以 Rufus 为例: - -1. **选择要写入的 U 盘** -2. **选择下载好的镜像** -3. **点击开始** - -进度条显示 "准备就绪" 即为完成,可拔出 U 盘。 - -## 3. 将系统安装到设备上 - -### 3.1 设备准备 - -- 将主机接入至少一块硬盘用于做系统盘。系统盘推荐选择 SSD 固态硬盘以获得更快的系统运行速度。 -- 将主机接好网线。暂时不支持只有无线网卡的情况下安装,后续有计划支持。 -- 接好电源、显示器、键盘、鼠标,鼠标非必需。 - -### 3.2 选择引导盘 - -将制作好的引导盘插入到主机的接口。按主机电源开机,开机同时快速按键盘热键进入boot 设备选择界面。不同设备主板进入boot 页面的热键不同,F2、F7、F8、F10、F11、F12、esc 、Delete 键都有可能。 - -选择引导盘,按回车,进入安装页面。 - -### 3.3 选择安装方式 - -推荐选择 Graphical Install(图形化安装流程),回车进入下一步。 - -> **注意:** -> - 如果页面提示 "暂不支持在当前模式下安装。请前往BIOS关闭 Secure Boot 后重试",请参考安装系统异常情况处理中第 1 点 -> - 如果在后续图形化安装步骤中遇到错误安装失败,请重启设备、在这一步选择 Rescuing Install(应急模式安装)重试。 - -### 3.4 选择系统盘 - -方向键切换选择要用于系统安装的硬盘,回车或 Alt+N 进入下一步。 - -### 3.5 设置系统分区和 Swap 大小 - -在系统盘容量充足的情况下,建议系统分区设置为 64 GB。如安装在虚拟机下,可按情况适当减小。Swap 分区大小已按物理内存大小计算推荐值,可按硬件情况自行调整。 - -> **注意:** 在 fnOS 上,系统将只安装在一块硬盘中。系统盘除系统分区以外的空间,可用于创建存储空间安装应用和存储文件。建议根据需要选择合适容量的 SSD 作为系统盘。 - -确认后,回车或 Alt+N 进入下一步。 - -### 3.6 确认格式化 - -所选择的系统盘将被格式化,所有内容将被删除,回车或 Alt+O 确认后开始安装。 - -### 3.7 设置网络 - -安装完成后将自动进入网络设置,系统将自动检测已连接的网卡并获取动态 IP。如网卡未开启 DHCP,你需要手动设置 IP。确认后,回车或 Alt+S 保存设置。 - -### 3.8 重启设备 - -网络设置保存成功后,你可以将引导盘从主机上拔出。回车或 Alt+O 重启系统。 - -### 3.9 重启成功进入终端 - -设备重启成功后将自动进入终端登录页,这意味着系统已成功安装并启动!请记住所设置的 IP 地址,接下来你将通过浏览器访问此地址来设置和使用 fnOS。现在你可以将显示器、键盘、鼠标从主机上拔除了。 - -## 4. 浏览器访问 fnOS - -在电脑浏览器上访问上一步所设置的 IP 地址。首次进入 fnOS,你需要完成 2 项简单的系统初始化设置。点击 **开始 NAS 之旅** 进入初始化。 - -### 4.1 设置设备名称 - -设备名称支持数字、英文及短横线(-),不能以短横线开头或结尾。 - -### 4.2 设置管理员账号 - -fnOS 用户分为管理员用户和普通用户,且支持创建多个管理员。首位管理员为超级管理员,相较于其他管理员的特殊性在于:创建后不能删除、只能停用,且默认开启 SSH 权限。请设置高强度的密码以提高账号安全性。 - -确认后,点击 **进入fnOS** 完成设置。 - -### 4.3 进入桌面 - -建议尽快创建存储空间以便上传文件和安装应用。如果硬盘还没有准备好,也可以点击 **稍后** 跳过,后续可从 **设置** > **存储空间管理** 中创建。 - -## 5. 下载安装 App - -推荐下载飞牛 App,具体方式请参考 [如何安装 App 并连接到飞牛 NAS?](./install-app.md) - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/start/install-tv.md b/fnhelp/start/install-tv.md deleted file mode 100644 index 888350f..0000000 --- a/fnhelp/start/install-tv.md +++ /dev/null @@ -1,60 +0,0 @@ -# 如何安装飞牛 TV 并连接到影视服务器? - -## 前置准备 - -1. 需要安装并且初始化飞牛 fnOS 系统; -2. 需要在飞牛 NAS 上,安装飞牛影视应用; -3. 若需要使用 U 盘安装到安卓 TV,需要准备 U 盘用以拷贝飞牛 TV 安装包,并将 U 盘插到 TV 设备安装。 - -## 1. 下载和安装飞牛 TV - -### 安卓 TV - -以下有 2 种方式可下载安装飞牛 TV 到安卓电视: - -**方式 1:U 盘安装** - -1. 在[飞牛官网](https://www.fnnas.com)下载飞牛 TV 的安装包 (.apk 文件); -2. 将安装包文件转移到 U 盘中; -3. 将带有安装包文件的 U 盘插入 TV 设备,并在设备中安装飞牛 TV。 - -**方式 2:各大应用商店下载安装** - -飞牛 TV 已上架以下安卓 TV 应用商店或电视品牌: -- 当贝市场、创维、酷开、海尔、暴风 -- 欢视商店、TCL、长虹、东芝、三洋 -- OPPO、联想 - -> 注意:由于电视品牌的机型和商店政策差异,可能仅覆盖部分机型。若您所使用的电视或商店不在其中,请选择方式 1(U 盘安装)。 - -### Apple TV - -1. 打开 Apple TV 中的 App Store; -2. 搜索"飞牛TV",找到飞牛 TV App; -3. 获取并安装飞牛 TV App。 - -## 2. 同意条款以继续使用 - -1. 完成安装后,在"应用"中找到飞牛 TV; -2. 打开飞牛 TV,同意飞牛的**服务条款**和**隐私协议**以继续使用。 - -## 3. 连接影视服务器并登录 - -### 3.1 连接影视服务器 - -- **自动发现**:打开飞牛 TV 后,自动发现当前局域网下的飞牛影视服务器。 - > 注意:如果没有发现服务器,请检查设备开机情况、网络是否正常、影视服务器是否已创建。 -- **手动添加服务器**:输入飞牛影视服务器的 IP 地址或配置的 DDNS 域名。 - -### 3.2 登录影视账号 - -- **扫码登录**:打开飞牛 App,进入"首页 > 左上角头像 > 更多 > 扫码",扫描电视上的二维码,并在 App 上同意授权。 -- **账号密码登录**:手动填写影视账号和密码。 - -### 3.3 成功连接并登录 - -成功连接并登录后,即可进入飞牛 TV。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/sync/fnsync-faq.md b/fnhelp/sync/fnsync-faq.md deleted file mode 100644 index 28fd04f..0000000 --- a/fnhelp/sync/fnsync-faq.md +++ /dev/null @@ -1,45 +0,0 @@ -# 飞牛同步 - 常见使用问题说明 - -## 1. 新设备连接不上 - -- **原因**:飞牛同步默认使用HTTPS连接,需要正确输入HTTPS端口。 -- **解决方案**:如果使用IP连接且未修改端口,默认端口为5667。 - -## 2. 任务显示状态异常/账号异常 - -### NAS 连接问题 - -- **可能原因**:NAS关闭、HTTPS端口被修改、账号密码变更等导致连接中断。 -- **解决方法**:通过"编辑"按钮更新NAS连接方式,重新授权。 - -### 文件变更问题 - -- **可能原因**:文件被删除、移动或重命名导致同步异常。 -- **解决方法**:通过"恢复"按钮重启任务,重新同步文件。 - -## 3. 传输项目与文件数量对不上 - -- **原因1**:同步任务包含子文件夹,创建文件夹也会被统计为传输项目。 -- **原因2**:电脑端隐藏文件未被同步(需取消隐藏才能同步)。 - -## 4. 电脑端出现了冲突副本文件 - -- **常见场景**: - - 多用户同时编辑同一文件。 - - 文件被占用(编辑或预览)。 - - 未基于最新版本修改文件。 -- **表现**:冲突文件会被重命名(含用户名、"冲突副本"字样和时间戳),且不会上传至NAS。 - -## 5. 电脑端文件一直没有被同步 - -- **原因**:文件被其他应用程序占用,操作系统限制无法监听。 -- **解决方法**:退出占用文件的应用程序后,同步任务会自动继续。 - -## 6. 已创建的同步任务是否可以编辑 - -- **可编辑项**:同步模式、文件过滤规则、NAS连接方式。 -- **不可编辑项**:电脑端和NAS端的同步文件夹路径(无法修改)。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/sync/how-to-use-fnsync.md b/fnhelp/sync/how-to-use-fnsync.md deleted file mode 100644 index 7136ca3..0000000 --- a/fnhelp/sync/how-to-use-fnsync.md +++ /dev/null @@ -1,64 +0,0 @@ -# 飞牛同步功能介绍 - -## 1. 下载对应的客户端 - -### Windows - -1. 电脑端浏览器打开飞牛官网,选中 Windows 下载应用文件; -2. 安装飞牛同步应用。 - -### Mac - -1. 电脑端浏览器打开飞牛官网,点击 Mac ,选择你的电脑对应的处理器版本下载应用文件; -2. 安装飞牛同步应用。 - -## 2. 连接 NAS - -### 连接新设备 - -点击左下角"创建新任务"打开"连接新 NAS 设备"弹窗,你可通过以下多种访问方式连接到 NAS: - -1. **输入 IP 地址、域名或 FN ID** -2. **发现局域网内设备** - -> **注意:** -> - 出于数据安全性考量,飞牛同步当前仅支持 HTTPS; -> - HTTPS 访问 fnOS 默认端口为 5667。 - -## 3. 登录授权 - -连接 NAS 设备成功后需进行账号授权。 - -> **注意:** -> - 授权后并不会将你的账号密码保存在客户端。 - -## 4. 创建任务 - -### 4.1 设置同步规则 - -1. **同步模式**:目前支持3种同步模式: - - **双向同步**:NAS 端和电脑互为镜像,两端文件完全保持一致。 - - **仅下载**:仅同步 NAS 数据至电脑;NAS 文件删除时,电脑端文件将保留。 - - **仅上传**:仅同步电脑数据至 NAS;电脑端文件删除时,NAS 文件将保留。 - -2. **更多设置-设置过滤规则**: - 可以根据需要自行定义规则,排除指定大小或格式的文件。 - > **注意:** `tmp` 和 `temp` 为系统强制过滤的文件扩展名。 - -### 4.2 设置同步路径 - -- **电脑文件路径**:选择电脑端上要同步的文件夹路径。 -- **NAS 文件路径**:选择 NAS 端上要同步的文件夹路径。 - -## 5. 查看任务 - -1. **主页面查看**:可通过任务卡片查看此任务的同步进度和同步状态。 -2. **系统托盘查看**:可通过系统任务栏中飞牛同步的icon快速打开托盘。 - -## 6. 系统设置 - -可通过「设置」功能对飞牛同步进行更新、限速等设置。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/virtual-machine/install.md b/fnhelp/virtual-machine/install.md deleted file mode 100644 index a9c362d..0000000 --- a/fnhelp/virtual-machine/install.md +++ /dev/null @@ -1,75 +0,0 @@ -# 如何安装和使用虚拟机? - -## 1. 安装应用 - -### 1.1 应用中心安装虚拟机 - -- 使用管理员账号登录飞牛 fnOS。 -- 打开 **应用中心**,搜索并找到 **虚拟机** 应用。 -- 点击 **安装**。 - -### 1.2 确认安装位置 - -- 虚拟机应用默认安装在系统分区。 -- 确认后点击 **下一步**。 - -### 1.3 检查信息 - -- 点击 **确定** 开始安装。 - -### 1.4 安装完成 - -- 安装完成后,桌面会出现 **虚拟机** 应用图标。 - -## 2. 使用准备 - -### 2.1 网络设置 - -- 在 **系统设置 > 网络管理** 中,为有线网口或聚合网口启用 OVS(Open vSwitch),以提供虚拟网络支持(无线网口暂不支持)。 - -### 2.2 驱动程序下载 - -- [VirtIO Guest Tool 驱动程序(适用于 Windows 7)](https://iso.liveupdate.fnnas.com/help_static/images/virtio-win-0.1.173.iso) -- [VirtIO Guest Tool 驱动程序(适用于 Windows 10 及以上)](https://iso.liveupdate.fnnas.com/help_static/images/virtio-win-0.1.266.iso) - -### 2.3 开启 CPU 虚拟化支持 - -- 进入 BIOS/UEFI 设置(开机按 `Del`、`F2`、`F10` 或 `Esc`)。 -- 启用 **Intel Virtualization Technology**(Intel CPU)或 **SVM Mode**(AMD CPU)。 -- 保存设置并重启。 - -## 3. 创建虚拟机 - -### Windows 系统创建步骤 - -1. 在 **虚拟机** 应用中点击 **创建虚拟机**。 -2. 输入虚拟机名称,选择操作系统类型和版本。 -3. 配置镜像和参数(如 ISO 文件、驱动文件、CPU 核心、内存等)。 -4. 添加虚拟磁盘(最多 8 个),选择总线类型(如 VirtIO)和存储空间。 -5. 添加虚拟网络(最多 8 个),选择 OVS 网口和网络类型。 -6. 点击 **创建**,完成后启动虚拟机并通过 VNC 连接。 - -### Linux 系统创建步骤 - -1. 同上步骤创建虚拟机,选择 Linux 类型和版本。 -2. 配置镜像和参数(无需额外驱动,Linux 自带 VirtIO 驱动)。 -3. 添加虚拟磁盘和虚拟网络。 -4. 创建并启动虚拟机。 - -## 4. 操作系统安装流程 - -### Windows 11 安装注意事项 - -- 选择 **Q35 + UEFI** 配置(支持 TPM 2.0)。 -- 在启动时按任意键进入安装流程。 -- 如使用 VirtIO 磁盘/网络,需加载对应驱动程序。 - -### VirtIO 存储驱动程序安装 - -1. 在 Windows 安装界面点击"加载驱动程序"。 -2. 选择驱动 ISO 中的对应目录(如 `amd64/win11`)。 -3. 安装 **Red Hat VirtIO SCSI Controller**。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/virtual-machine/passthrough.md b/fnhelp/virtual-machine/passthrough.md deleted file mode 100644 index 033032d..0000000 --- a/fnhelp/virtual-machine/passthrough.md +++ /dev/null @@ -1,51 +0,0 @@ -# 如何使用虚拟机硬件直通? - -硬件直通是一种虚拟化技术,它允许虚拟机直接访问宿主机上的物理硬件设备,而不是通过虚拟化层进行仿真。 - -## 应用场景 - -1. **显卡直通**:支持物理显示器输出,提升图形处理性能; -2. **声卡直通**:实现音响或耳机的高质量音频输出; -3. **USB设备直通**:便于读取USB存储设备或连接其他USB设备; -4. **网卡直通**:满足网络加速和复杂的家庭组网需求。 - -> **【硬件直通的风险和限制】** -> -> 硬件直通可能带来一定风险和限制,错误配置可能导致**设备冲突**或**系统崩溃**。在操作前,请充分了解以下风险: -> -> 1. **存储直通风险**:错误直通SATA、NVMe或USB存储设备可能损坏宿主机上的数据,且不可逆,请务必提前备份; -> 2. **网卡直通风险**:请确保宿主机保留可用网卡,否则可能导致宿主机网络中断; -> 3. **显卡直通风险**:直通显卡成功率并不高,且有可能造成宿主机崩溃,需谨慎操作。 -> -> 不建议非专业用户尝试硬件直通操作。 - -## 1. 开启硬件直通选项 - -打开虚拟机应用,在侧边栏点击 **硬件**,找到 **硬件直通选项** 的 **开启** 按钮,在仔细阅读 **《硬件直通技术免责声明》** 后, 点击 **确认开启**。 - -## 2. 虚拟机配置硬件直通 - -在创建虚拟机或编辑虚拟机界面, **添加设备** 按钮在 **硬件直通选项** 开启后便激活可点击。 - -### 2.1 添加 USB 设备 - -1. 点击 **添加硬件** 按钮,点击 **添加 USB 设备** -2. 选择需要直通给当前虚拟机的 USB 设备 -3. 点击 **确定** 按钮保存 - -### 2.2 添加 PCI 设备 - -1. 点击 **添加硬件** 按钮,点击 **添加 PCI 设备** -2. 在列表中选择需要直通的 PCI 设备 -3. 由于 PCI 设备根据 IOMMU 分组进行内存隔离,所以直通时需要将同一 IOMMU 分组的 PCI 设备一并直通 -4. 点击 **确定** 按钮保存 - -## 3. 虚拟机详细日志指引 - -由于多方面因素影响,虚拟机可能启动失败,可在日志中查询相关详细原因。 - -日志路径:`/var/apps/trim.vm/var/log/error.log` - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/volume/create.md b/fnhelp/volume/create.md deleted file mode 100644 index b264912..0000000 --- a/fnhelp/volume/create.md +++ /dev/null @@ -1,52 +0,0 @@ -# 如何创建存储空间? - -本文详细介绍了在飞牛 fnOS 系统中创建存储空间的步骤,适用于管理员用户通过网页端或 App 操作。 - -## 创建存储空间的核心步骤 - -### 1. 选择文件系统 - -支持 Btrfs、ZFS 和 ext4 三种文件系统(自 V0.9.35 版本起)。用户需根据使用需求、硬件性能等因素选择。 - -### 2. 选择硬盘 - -从左侧列表中选择一个或多个硬盘。 - -> **注意:** -> - 不同类型、接口协议、连接方式或逻辑扇区大小的硬盘不能混合组存储空间。 -> - 系统盘剩余容量可单独创建存储空间,但不能组 RAID 或扩容。 -> - 外接硬盘需确保接口牢固,避免因松动导致存储空间异常。 - -### 3. 选择存储模式 - -根据所选文件系统和硬盘数量,系统会动态显示可用的存储模式(如 RAID 类型)。 - -确认预计容量后点击"下一步"。 - -### 4. 设置用户权限 - -- **管理员**:默认无容量上限,如需限制需在"用户管理"中单独设置。 -- **普通用户**:可指定允许使用的用户及其容量上限。 - -### 5. 确认信息 - -可填写存储空间描述(可选),并选择是否创建后自动执行硬盘读写检测。 - -### 6. 确认格式化 - -系统会提示硬盘将被格式化,所有数据丢失。需输入管理员密码确认。 - -### 7. 创建成功 - -完成上述步骤后,存储空间即创建成功。 - -## 关键注意事项 - -- **兼容性**:硬盘需满足类型、接口等一致性要求。 -- **系统盘限制**:系统盘剩余空间不能用于 RAID 或扩容。 -- **外接硬盘风险**:需确保接口稳定,避免因松动引发问题。 -- **权限管理**:管理员可全局访问存储空间,普通用户需单独授权。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/volume/hibernate.md b/fnhelp/volume/hibernate.md deleted file mode 100644 index 6d0a405..0000000 --- a/fnhelp/volume/hibernate.md +++ /dev/null @@ -1,31 +0,0 @@ -# 硬盘会自动休眠吗? - -fnOS 支持硬盘在闲时自动休眠,包括除了系统盘外的内置盘和支持休眠的外接盘。默认开启,修改设置方式如下: - -使用管理员账号打开网页端 **设置** > **硬盘信息** > **硬盘休眠**,设置内置和外接硬盘进入休眠的时间。 - -## 硬盘自动唤醒偏好 - -自系统版本 v0.9.10 起新增支持设置**硬盘自动唤醒偏好**。当开启了硬盘休眠后,可选择硬盘唤醒的策略: - -### 1. 按需唤醒:仅在对存储空间读写数据时 - -选择此选项后,系统只在读写数据时唤醒硬盘,不被读写的硬盘仍将保持休眠状态。 - -### 2. 响应更快:有用户登录即唤醒 - -选择此选项后,除了读写数据会唤醒硬盘外,有用户登录网页端、App时,系统也将自动唤醒全部硬盘,以提升读写操作时的响应速度,使用体验更丝滑。 - -> **注意:** -> -> - 常见会触发读写硬盘的操作有: -> - 访问文件管理; -> - 硬盘所在的存储空间上有正在执行的文件任务,包括上传、下载、移动、复制等; -> - 应用或 Docker 安装在硬盘所在的存储空间上,且正在运行产生传输; -> - 应用或 Docker 正在访问存储空间上的文件,包括影视刮削播放、相册查看和 AI 识别等; -> - 下载类应用或 Docker 容器中有任务正在下载或做种。 -> - 除了读写文件外,访问 **系统设置 > 存储空间管理** 和 **硬盘信息** 页面时,由于需要查询存储相关信息,也会唤醒已被用于存储空间的所有硬盘。如不希望唤醒,可通过 **资源监控** 查看硬盘状态和信息。 - ---- - -*来源:飞牛 fnOS 帮助中心* diff --git a/fnhelp/volume/ssd-cache.md b/fnhelp/volume/ssd-cache.md deleted file mode 100644 index 9147187..0000000 --- a/fnhelp/volume/ssd-cache.md +++ /dev/null @@ -1,62 +0,0 @@ -# 如何使用 SSD 缓存加速? - -SSD 缓存加速功能是指将固态硬盘 (SSD) 作为存储空间的缓存,从而将频繁访问的数据缓存到高速 SSD 上,以此提升日常使用中的读写性能。 - -## 注意事项 - -1. 创建 SSD 缓存的前提条件是:有至少1 块内置的固态硬盘 (SSD) 未被用于创建存储空间,且容量大于10G。系统盘除系统分区外的剩余空间如果满足上述条件,也可以被用于创建 SSD 缓存。这将增加系统盘的读写,因此可能缩短系统盘的寿命,请谨慎选择。 -2. 一个 SSD 缓存可以被分配给多个存储空间。创建时分配容量有剩余的,创建完成后可以继续分配给其他存储空间。 -3. 多块硬盘组成 SSD 时可选择阵列模式,以获得数据保护或速度上的提升。 -4. 有 2 种缓存模式可选:只读缓存和读写缓存(ZFS 下为写入缓存)。不稳定的盘做读写缓存容易造成数据丢失、导致整个存储空间损坏,请谨慎选择。 -5. 如要删除读写模式的 SSD 缓存,请务必从界面上操作,切勿直接将 SSD 从设备中拔出,否则数据可能会丢失。 -6. 暂不支持对已经创建好的缓存进行扩容、更换硬盘、修改存储模式等操作,如有需要,请删除缓存后重建。 - -## 创建 SSD 缓存 - -登录管理员账号,从桌面打开 **系统设置** > **存储空间管理** > **SSD 缓存加速** ,点击 **创建SSD 缓存**。 - -### 1. 选择存储空间 - -选择要使用 SSD 缓存的存储空间。 - -请注意,对于 Btrfs 和 ext4 的存储空间,虽然创建时只能选择一个存储空间,但如果分配后缓存的剩余容量大于 10G,你仍可将剩余容量分配给其他存储空间,分配方式详见下文《3. 分配 SSD 缓存容量》。而对于 ZFS 的存储空间,则无法多个存储空间共用一个 SSD 缓存。 - -### 2. 选择缓存模式 - -对于 Btrfs 和 ext4 的存储空间,可选择只读缓存或读写缓存。只读缓存只缓存常读取的数据,适用于读多写少的场景。读写缓存适用于高频写入的场景。 - -对于 ZFS 的存储空间,可选择只读缓存(L2ARC)或读写缓存(SLOG)。ZFS 默认使用物理内存作为一级缓存(ARC)。添加 L2ARC 将加速常用文件读取,适合读多写少的场景,但将增加内存占用。SLOG 将加速同步写入操作,适合频繁写入或虚拟化等需要高数据一致性的场景。 - -### 3. 选择硬盘和存储模式 - -在页面左侧选择要用于创建 SSD 缓存的 1 个或多个硬盘。根据所选的硬盘个数,右侧将动态显示可供选择的存储模式。选择存储模式后,可以在弹窗底部确认缓存的预计容量情况。确认无误后,点击 **下一步**。 - -> 注意:如果缓存的存储模式不具备数据保护能力(如 Basic、RAID 0),且选择了读写缓存(或 ZFS 的写入缓存)模式,则在 SSD 损坏、掉盘或非正常移除时,有较大概率导致数据丢失,请谨慎选择。 - -### 4. 分配缓存容量 - -对于 Btrfs 和 ext4 的存储空间,可指定要分配给此存储空间的缓存容量,剩余容量可分配给其他存储空间。 - -### 5. 确认信息 - -确认所选的存储空间、缓存模式、硬盘及存储模式、分配容量等信息。 - -### 6. 确认格式化 - -用于组缓存的硬盘将被格式化,所有数据将被删除。请输入管理员密码进行最终确认。 - -### 7. 创建成功 - -缓存创建完成后,可以在 SSD 缓存加速页面查看缓存状态。 - -## 分配 SSD 缓存容量 - -对于 Btrfs 和 ext4 的存储空间,多个存储空间可共用同个 SSD 缓存。当 SSD 缓存存在未分配的分区时,点击 **分配**,选择存储空间、缓存模式及分配容量,点击确认。输入管理员密码,点击开始分配。分配成功后,缓存容量将被分配给指定存储空间。 - -## 删除 SSD 缓存 - -当缓存仅被分配给一个存储空间时,点击 **缓存** > **更多** > **删除**,可删除缓存。当缓存被分配给多个存储空间时,点击 **缓存空间容量分配** > **移除**,分别将所有分区移除后,缓存将自动被删除。请注意,移除读写缓存时需要将已缓存的数据回写到存储空间中,这可能会需要一些时间。 - ---- - -*来源:飞牛 fnOS 帮助中心*