之前用树莓派改造的旁路网关性能太差了,不管是互联网速度还是内网速度都很捉急。刚好家中有台吃灰的笔记本电脑(i7-6500U + 16G),于是决定趁着周末把它改造成一台 All in One NAS。
明确需求
在一切开始之前,首先梳理一下对这台 NAS 的需求:
-
可以充当旁路网关,实现全局透明赛博旅游
-
需要能够通过 DHCP 分设备设置网关
-
最好要有 Web UI,方便更新路由器配置
-
-
可以为台式机、平板电脑、笔记本电脑提供 NAS 功能(SMB、NFS、WebDav)
之前在树莓派上面实现第一个需求是直接用的 dhcpd,每次修改 DHCP 配置都要先去路由器后台查看设备 MAC 地址,再手动去树莓派上改文件,麻烦且不够直观。网上一番调研后发现只有 OpenWRT 才比较容易满足我的需求,然而直接把版基本电脑装成 OpenWRT 有点大材小用,所以还得整个虚拟机来运行。
第二个 NAS 需求的话,网上推荐的都是 OpenMediaVault 这个基于 Debian 的系统,集成了 NFS、SMB等多种文件共享方案,还提供了一个 Web 管理 UI。愿意折腾的话,还可以安装各种第三方插件拓展现有功能,甚至可以直接通过插件管理 KVM 虚拟机、docker 容器。
最后,我选择了在宿主机上安装 PVE,一个专门用来提供虚拟化环境的系统。在 PVE 的基础上,再创建 OpenWRT 跟 OpenMediaVault 的虚拟机,专事专干。
安装 OpenWRT
安装 OpenWrt 也比较简单,照着 这篇文章 的步骤来操作就好了。不过在配置 OpenWRT 的全局透明代理的时候,有些地方需要注意。我选用的是 Xray 的 全局透明代理方案,这个方案会拦截局域网发往 53 端口的流量,所以不存在 DNS 污染的问题。不过,官方文档中给出的配置在我们的旁路由场景上需要 修改一下,因为 OpenWRT 自带 dnsmasq,会通过 127.0.0.1 向自己的 53 端口发起 DNS 查询,所以我们需要在官方文档的基础上进一步拦截来自 localhost 的 DNS 查询:
iptables -t mangle -N XRAY
iptables -t mangle -A XRAY -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY -d 100.64.0.0/10 -j RETURN
iptables -t mangle -A XRAY -d 127.0.0.0/8 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A XRAY -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A XRAY -d 192.0.0.0/24 -j RETURN
iptables -t mangle -A XRAY -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY -d 240.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1
iptables -t mangle -A PREROUTING -j XRAY
iptables -t mangle -N XRAY_SELF
iptables -t mangle -A XRAY_SELF -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A XRAY_SELF -d 100.64.0.0/10 -j RETURN
iptables -t mangle -A XRAY_SELF -d 127.0.0.0/8 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY_SELF -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A XRAY_SELF -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.0.0.0/24 -j RETURN
iptables -t mangle -A XRAY_SELF -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY_SELF -d 240.0.0.0/4 -j RETURN
iptables -t mangle -A XRAY_SELF -d 255.255.255.255/32 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY_SELF -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN
iptables -t mangle -A XRAY_SELF -m mark --mark 2 -j RETURN
iptables -t mangle -A XRAY_SELF -p tcp -j MARK --set-mark 1
iptables -t mangle -A XRAY_SELF -p udp -j MARK --set-mark 1
iptables -t mangle -A OUTPUT -j XRAY_SELF
按设备指定网关
为了能够享受到赛博旅游的好处,我会通过 DHCP 来把局域网内的所有设备的网关设置为旁路由,通常情况下都没啥问题。不过在某些 IoT 设备 指米家 WiFi 物联网家电 上,就算设置了白名单规则,还是会有无法连接服务器的情况发生。这个时候就需要按设备指定网关地址了,还好这个功能在 OpenWrt 上还是比较好实现的。只需要 修改 dhcp 配置,给需要直连的设备标记上 tag:
config tag 'direct'
list dhcp_option '3,192.168.1.1' (1)
option force '1'
config host
option ip '192.168.1.111'
option mac '3a:58:e2:74:73:5e'
option name 'Thinkpad'
option dns '1'
option tag 'direct' (2)
1 | 3,xxx 表示设置网关地址 |
2 | 给指定的设备设置 tag |
安装 OpenMediaVault
考虑到我的硬盘并不多,而且也没有给虚拟机直通硬盘的打算,所以就直接用 PVE 建立了一个 ZFS Pool,用来给虚拟机分配引导盘跟数据盘。OpenMediaVault 也是用的虚拟硬盘来做数据存储,在我的场景下,性能应该是够用的。虚拟机的安装过程就不在这里赘述了,跟安装普通的 Linux 系统并没有啥区别。
全新安装的 OMV 自带 NFS、SMB、Rsync 这些常用的文件共享服务,但还是缺少 WebDav 协议,很多应用都可以通过 WebDav 进行同步备份,所以这个功能必须得补上。我选择的方案就是 rclone serve webdav。直接建立一个开机自启项,通过 rclone 在内网提供一个不需要身份验证的 WevDav 服务:
[Unit]
Description=Webdav - rclone
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/bin/rclone serve webdav --addr :8080 /path/to/public/dir
Restart=on-failure
[Install]
WantedBy=default.target
用 Docker 整些花活
我给 OpenMediaVault 虚拟机的定位非常清楚,就是专门用来提供 NAS 的基础功能的,所以不打算在它上面装各种奇奇怪怪的东西。但我一直都有建立一个电子相册的想法,所以就准备直接 用 Docker 运行一个 PhotoPrism。我的 docker-compose.yml 如下:
version: '3.5'
# Example Docker Compose config file for PhotoPrism (Linux / AMD64)
#
# Documentation : https://docs.photoprism.org/getting-started/docker-compose/
# Docker Hub URL: https://hub.docker.com/r/photoprism/photoprism/
#
# Please run behind a reverse proxy like Caddy, Traefik or Nginx if you need HTTPS / SSL support
# e.g. when running PhotoPrism on a public server outside your home network.
#
# ------------------------------------------------------------------
# DOCKER COMPOSE COMMAND REFERENCE
# ------------------------------------------------------------------
# Start | docker-compose up -d
# Stop | docker-compose stop
# Update | docker-compose pull
# Logs | docker-compose logs --tail=25 -f
# Terminal | docker-compose exec photoprism bash
# Help | docker-compose exec photoprism photoprism help
# Config | docker-compose exec photoprism photoprism config
# Reset | docker-compose exec photoprism photoprism reset
# Backup | docker-compose exec photoprism photoprism backup -a -i
# Restore | docker-compose exec photoprism photoprism restore -a -i
# Index | docker-compose exec photoprism photoprism index
# Reindex | docker-compose exec photoprism photoprism index -f
# Import | docker-compose exec photoprism photoprism import
#
# To search originals for faces without a complete rescan:
# docker-compose exec photoprism photoprism faces index
# -------------------------------------------------------------------
# Note: All commands may have to be prefixed with "sudo" when not running as root.
# This will change the home directory "~" to "/root" in your configuration.
services:
photoprism:
# Use photoprism/photoprism:preview instead for testing preview builds:
image: photoprism/photoprism:latest
# Only enable automatic restarts once your installation is properly
# configured as it otherwise may get stuck in a restart loop:
# https://docs.photoprism.org/getting-started/faq/#why-is-photoprism-getting-stuck-in-a-restart-loop
# restart: unless-stopped
security_opt:
- seccomp:unconfined
- apparmor:unconfined
# Run as a specific, non-root user (see https://docs.docker.com/engine/reference/run/#user):
# user: "1000:1000"
ports:
- "2342:2342" # [server]:[container]
environment:
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # PLEASE CHANGE: Your initial admin password (min 4 characters)
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # File size limit for originals in MB (increase for high-res video)
PHOTOPRISM_HTTP_COMPRESSION: "gzip" # Improves transfer speed and bandwidth utilization (none or gzip)
PHOTOPRISM_DEBUG: "false" # Run in debug mode (shows additional log messages)
PHOTOPRISM_PUBLIC: "false" # No authentication required (disables password protection)
PHOTOPRISM_READONLY: "false" # Don't modify originals directory (reduced functionality)
PHOTOPRISM_EXPERIMENTAL: "false" # Enables experimental features
PHOTOPRISM_DISABLE_CHOWN: "false" # Disables storage permission updates on startup
PHOTOPRISM_DISABLE_WEBDAV: "false" # Disables built-in WebDAV server
PHOTOPRISM_DISABLE_SETTINGS: "false" # Disables Settings in Web UI
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # Disables all features depending on TensorFlow
PHOTOPRISM_DISABLE_FACES: "false" # Disables facial recognition
PHOTOPRISM_DISABLE_CLASSIFICATION: "false" # Disables image classification
PHOTOPRISM_DARKTABLE_PRESETS: "true" # Enables Darktable presets and disables concurrent RAW conversion
PHOTOPRISM_DETECT_NSFW: "false" # Flag photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "true" # Allow uploads that MAY be offensive
PHOTOPRISM_SITE_URL: "http://localhost:2342/" # Public PhotoPrism URL
PHOTOPRISM_SITE_TITLE: "PhotoPrism"
PHOTOPRISM_SITE_CAPTION: "Browse Your Life"
PHOTOPRISM_SITE_DESCRIPTION: ""
PHOTOPRISM_SITE_AUTHOR: ""
# Set a non-root user, group, or custom umask if your Docker environment doesn't support this natively:
# PHOTOPRISM_UID: 1000
# PHOTOPRISM_GID: 1000
# PHOTOPRISM_UMASK: 0000
# Enable TensorFlow AVX2 support for modern Intel CPUs (requires starting the container as root):
# PHOTOPRISM_INIT: "tensorflow-amd64-avx2"
# Hardware video transcoding options:
# PHOTOPRISM_FFMPEG_BUFFERS: "64" # FFmpeg capture buffers (default: 32)
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_v4l2m2m" # Use Video4Linux for AVC transcoding (default: libx264)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_qsv" # Use Intel Quick Sync Video for AVC transcoding (default: libx264)
# PHOTOPRISM_INIT: "intel-graphics tensorflow-amd64-avx2" # Enable TensorFlow AVX2 & Intel Graphics support
HOME: "/photoprism"
# Optional hardware devices for video transcoding and machine learning:
# devices:
# - "/dev/video11:/dev/video11" # Video4Linux (h264_v4l2m2m)
# - "/dev/dri/renderD128:/dev/dri/renderD128" # Intel GPU
# - "/dev/dri/card0:/dev/dri/card0"
working_dir: "/photoprism"
volumes:
# Your photo and video files ([local path]:[container path]):
- "zeeko-pictures:/photoprism/originals"
# Multiple folders can be indexed by mounting them as sub-folders of /photoprism/originals:
# - "/mnt/Family:/photoprism/originals/Family" # [folder_1]:/photoprism/originals/[folder_1]
# - "/mnt/Friends:/photoprism/originals/Friends" # [folder_2]:/photoprism/originals/[folder_2]
# Mounting an import folder is optional (see docs):
# - "~/Import:/photoprism/import"
# Permanent storage for settings, index & sidecar files (DON'T REMOVE):
- "apps_photos:/photoprism/storage"
volumes:
zeeko-pictures: (1)
external: true
apps_photos: (2)
external: true
1 | 一个通过 NFS 挂载的 volume,保存图片的位置 |
2 | 另一个通过 NFS 挂载的 volume,用来持久化配置文件,因为 PhotoPrism 容器初始化的时候会对配置文件夹使用 chmod ,在 NFS 上会报权限错误,一个比较懒的解决方案是 NFS 共享时添加 no_root_squash 选项。 |