WIN10 无人值守 · 全自动恢复 · v3.0

仓库 NVR
远程访问方案设计TAILSCALE + NETSH PORTPROXY · MULTI-SITE

每台 Windows 10 工控机作为 Tailscale 端口转发网关,用系统原生 netsh portproxy 做端口映射, 配合 任务计划程序实现开机重建与定时巡检,做到长期运行、断网自愈、零人工干预。各仓库内网网段可完全相同,路由器品牌无要求。

Tailscale 虚拟组网 netsh portproxy 任务计划程序 5 分钟健康巡检 ACL + 防火墙双控
方案分层信号 ALL SYSTEMS NOMINAL
NETTailscale 虚拟网100.x.x.x / 10 网段 · 自动重连
PROXYnetsh interface portproxy系统原生 · 零第三方依赖
KEEP任务计划程序 ×2开机重建 + 每 5 分钟巡检
EDGEACL + Windows 防火墙按端口 + 设备精确授权
CONFIGC:\NVR-Gateway\config.json集中配置 · 改完 restart 即生效
3+
并行仓库节点,网段可完全重复
2
统一入口端口 8000 / 8554
5min
健康检查间隔,自动重建转发
0
日常人工干预,开机即恢复
01

架构总览

管理中心 → Tailscale → 工控机网关 → 路由器 → NVR
管理中心 / 个人设备 笔记本 · 手机 · Tailscale 客户端 TAILSCALE 虚拟网络 · 加密隧道 100.x.x.x / 10 网段 · 自动 NAT 穿透 · 断线自动重连 工控机 A TS 网关 Windows 10 · 无人值守 :8000 :8554 netsh portproxy · 任务计划保活 路由器 A 任意品牌 · 无需配置 NVR 1 192.168.1.100 仓库 A 工控机 B TS 网关 Windows 10 · 无人值守 :8000 :8554 netsh portproxy · 任务计划保活 路由器 B 任意品牌 · 无需配置 NVR 2 192.168.1.100 仓库 B 工控机 C TS 网关 Windows 10 · 无人值守 :8000 :8554 netsh portproxy · 任务计划保活 路由器 C 任意品牌 · 无需配置 NVR 3 192.168.1.100 仓库 C
访问端 Tailscale 加密隧道 工控机网关 仓库内网(网段可重复)
核心设计

每台 Win10 工控机作为 Tailscale 端口转发网关,使用系统原生 netsh portproxy 做端口映射, 通过 任务计划程序实现开机重建与定时巡检的持久化保活,配合 PowerShell 健康检查脚本自动恢复丢失的转发规则。

02

方案选型说明

为什么选端口转发而不是子网路由
对比项子网路由方案端口转发方案(本方案采用)
网段要求各仓库网段必须不同各仓库网段可以完全相同
路由器依赖需支持静态路由或允许 ICMP无要求 任意路由器均可
ACL 粒度按网段控制按 端口 + 设备 精确控制
配置复杂度需批准路由、开启 accept-routes即装即用 客户端无需额外配置
扩展性新增设备需改路由宣告改配置文件即可
结论

多仓库、多品牌路由器、网段可能冲突的场景下,端口转发更稳定可控 —— 这是本方案的唯一选择。

03

统一端口规划

所有工控机统一规范 · 便于记忆与批量管理
工控机监听端口映射目标用途协议
8000NVR 主机的 80NVR Web 管理界面HTTP
8554NVR 主机的 554RTSP 视频流RTSP
8001NVR2 的 80第二台 NVR Web(如有)HTTP
8555NVR2 的 554第二台 NVR RTSP(如有)RTSP

访问方式统一为 http://<工控机TS-IP>:8000

04

部署前准备

工具下载与系统要求确认
Tailscale VPN 组网客户端,工控机与访问端均需安装并登录同一 tailnet。 tailscale.com/download/windows
NSSM Windows 服务封装工具。将 nssm.exe 复制到 C:\Windows\System32\ 或任意 PATH 目录,以便全局调用。 nssm.cc/download
实测提示

本方案最终实现未使用 NSSM,而是用 Windows 任务计划程序完成「开机重建 + 定时巡检」—— 效果等价、零额外依赖、无需注册服务。NSSM 仅作为备选方案保留在工具清单中。

4.2

系统要求确认

操作系统版本

Windows 10 专业版 / 企业版 / IoT 企业版。家庭版部分功能受限,但端口转发可用。

Pro / Enterprise / IoT
IP Helper 服务

iphlpsvc 必须启用(默认开启),netsh portproxy 依赖此服务。

iphlpsvc = Running
防火墙放行

允许 Tailscale 与转发端口(8000 / 8554 / 8001 / 8555)入站,脚本会自动创建规则。

Inbound TCP Allow
05

一键部署脚本

PowerShell · 管理员身份执行 · 8 步全自动
setup-gateway.ps1
#Requires -RunAsAdministrator
# ============================================
# 仓库 Tailscale 网关一键配置脚本(Windows 10 版)
# 功能:安装 Tailscale + 配置 NVR 端口转发 + 持久化 + 保活
# ============================================

param(
    [string]$GatewayName = "warehouse-a",
    [string]$TailscaleAuthKey = "",
    [string]$NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp"
    # 格式: "本地端口,NVR内网IP,NVR端口,服务名;..."
)

$ErrorActionPreference = "Stop"

# ---------- 配置区 ----------
$InstallDir = "C:\NVR-Gateway"
$LogDir     = "$InstallDir\Logs"
$ConfigFile = "$InstallDir\config.json"

# 解析 NVR 配置
$NvrList = @()
foreach ($item in $NvrConfig -split ";") {
    $parts = $item -split ","
    $NvrList += [PSCustomObject]@{
        LocalPort = [int]$parts[0]
        NvrIp     = $parts[1]
        NvrPort   = [int]$parts[2]
        SvcName   = $parts[3]
    }
}

Write-Host "=== [$GatewayName] 网关部署开始 ===" -ForegroundColor Cyan

# 1. 创建目录
Write-Host "[1/8] 创建目录结构..." -ForegroundColor Yellow
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
New-Item -ItemType Directory -Path "$InstallDir\Scripts" -Force | Out-Null

# 2. 保存配置文件(JSON,便于后续修改和读取)
Write-Host "[2/8] 保存配置文件..." -ForegroundColor Yellow
$config = @{
    GatewayName = $GatewayName
    NvrList     = $NvrList
    InstallDir  = $InstallDir
    LogDir      = $LogDir
}
$config | ConvertTo-Json -Depth 3 | Out-File -FilePath $ConfigFile -Encoding UTF8

# 3. 检查并安装 Tailscale
Write-Host "[3/8] 检查 Tailscale..." -ForegroundColor Yellow
$tsPath = "${env:ProgramFiles}\Tailscale\tailscale.exe"
if (-not (Test-Path $tsPath)) {
    Write-Host "Tailscale 未安装,请手动下载安装后重新运行脚本" -ForegroundColor Red
    Write-Host "下载地址: https://tailscale.com/download/windows" -ForegroundColor Yellow
    Start-Process "https://tailscale.com/download/windows"
    Read-Host "安装完成后按回车继续..."
    $tsPath = "${env:ProgramFiles}\Tailscale\tailscale.exe"
}

# 4. 启动/登录 Tailscale
Write-Host "[4/8] 启动 Tailscale..." -ForegroundColor Yellow
if ($TailscaleAuthKey) {
    & $tsPath up --authkey="$TailscaleAuthKey" --hostname="$GatewayName" --accept-dns=false --ssh
} else {
    Write-Host ">>> 请执行以下命令完成登录(完成后按回车继续):" -ForegroundColor Green
    Write-Host "    tailscale up --hostname=$GatewayName --accept-dns=false --ssh" -ForegroundColor White
    Read-Host
}

Start-Sleep -Seconds 3
$tsIp = & $tsPath ip -4 2>$null
Write-Host ">>> Tailscale IP: $tsIp" -ForegroundColor Green

# 5. 配置 netsh 端口转发
Write-Host "[5/8] 配置端口转发..." -ForegroundColor Yellow
foreach ($nvr in $NvrList) {
    $listenPort = $nvr.LocalPort
    $targetIp   = $nvr.NvrIp
    $targetPort = $nvr.NvrPort
    $svcName    = $nvr.SvcName

    # 先删除可能存在的旧规则
    netsh interface portproxy delete v4tov4 listenport=$listenPort listenaddress=0.0.0.0 2>$null | Out-Null

    # 添加新规则
    netsh interface portproxy add v4tov4 listenport=$listenPort listenaddress=0.0.0.0 connectport=$targetPort connectaddress=$targetIp

    Write-Host "    OK $svcName : 0.0.0.0:$listenPort -> $targetIp`:$targetPort" -ForegroundColor Green
}

# 6. 配置 Windows 防火墙
Write-Host "[6/8] 配置防火墙规则..." -ForegroundColor Yellow
foreach ($nvr in $NvrList) {
    $listenPort = $nvr.LocalPort
    $ruleName = "NVR-Gateway-Port-$listenPort"

    # 删除旧规则
    Remove-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue

    # 添加入站规则(仅允许 Tailscale 虚拟网卡接口,或全部接口)
    New-NetFirewallRule `
        -DisplayName $ruleName `
        -Direction Inbound `
        -Protocol TCP `
        -LocalPort $listenPort `
        -Action Allow `
        -Profile Any `
        -ErrorAction SilentlyContinue | Out-Null

    Write-Host "    OK 防火墙规则: $ruleName (端口 $listenPort)" -ForegroundColor Green
}

# 7. 生成健康检查脚本
Write-Host "[7/8] 生成健康检查脚本..." -ForegroundColor Yellow
$healthCheckScript = @'
param([string]$ConfigFile = "C:\NVR-Gateway\config.json")

$cfg = Get-Content $ConfigFile | ConvertFrom-Json
$logFile = "$($cfg.LogDir)\health-check.log"

function Write-Log($msg) {
    $line = "[{0:yyyy-MM-dd HH:mm:ss}] {1}" -f (Get-Date), $msg
    Add-Content -Path $logFile -Value $line
    Write-Host $line
}

foreach ($nvr in $cfg.NvrList) {
    $tcp = New-Object System.Net.Sockets.TcpClient
    try {
        $tcp.Connect($nvr.NvrIp, $nvr.NvrPort)
        $tcp.Close()
        Write-Log ("OK {0} ({1}:{2}) 健康" -f $nvr.SvcName, $nvr.NvrIp, $nvr.NvrPort)
    }
    catch {
        Write-Log ("NG {0} ({1}:{2}) 不可达,正在重建端口转发..." -f $nvr.SvcName, $nvr.NvrIp, $nvr.NvrPort)
        netsh interface portproxy delete v4tov4 listenport=$($nvr.LocalPort) listenaddress=0.0.0.0 2>$null | Out-Null
        netsh interface portproxy add v4tov4 listenport=$($nvr.LocalPort) listenaddress=0.0.0.0 connectport=$($nvr.NvrPort) connectaddress=$($nvr.NvrIp)
        Write-Log ("REBUILD {0} 端口转发已重建" -f $nvr.SvcName)
    }
}
'@
$healthCheckScript | Out-File -FilePath "$InstallDir\Scripts\health-check.ps1" -Encoding UTF8

# 8. 注册 Windows 计划任务(定时健康检查 + 开机自启重建转发)
Write-Host "[8/8] 注册计划任务..." -ForegroundColor Yellow

# 8.1 健康检查任务(每 5 分钟)
$healthTaskName = "NVR-Gateway-HealthCheck"
Unregister-ScheduledTask -TaskName $healthTaskName -Confirm:$false -ErrorAction SilentlyContinue

$healthAction = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-ExecutionPolicy Bypass -File `"$InstallDir\Scripts\health-check.ps1`""
$healthTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 3650)
$healthSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable
Register-ScheduledTask -TaskName $healthTaskName -Action $healthAction -Trigger $healthTrigger -Settings $healthSettings -RunLevel Highest -Force | Out-Null
Write-Host "    OK 健康检查任务: 每 5 分钟执行" -ForegroundColor Green

# 8.2 开机重建转发任务(系统启动时执行)
$bootTaskName = "NVR-Gateway-BootSetup"
Unregister-ScheduledTask -TaskName $bootTaskName -Confirm:$false -ErrorAction SilentlyContinue

$bootScript = @"
`$cfg = Get-Content '$ConfigFile' | ConvertFrom-Json
foreach (`$nvr in `$cfg.NvrList) {
    netsh interface portproxy delete v4tov4 listenport=`$(`$nvr.LocalPort) listenaddress=0.0.0.0 2>`$null | Out-Null
    netsh interface portproxy add v4tov4 listenport=`$(`$nvr.LocalPort) listenaddress=0.0.0.0 connectport=`$(`$nvr.NvrPort) connectaddress=`$(`$nvr.NvrIp)
}
"@
$bootScript | Out-File -FilePath "$InstallDir\Scripts\boot-setup.ps1" -Encoding UTF8

$bootAction = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-ExecutionPolicy Bypass -File `"$InstallDir\Scripts\boot-setup.ps1`""
$bootTrigger = New-ScheduledTaskTrigger -AtStartup
$bootSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $bootTaskName -Action $bootAction -Trigger $bootTrigger -Settings $bootSettings -RunLevel Highest -Force | Out-Null
Write-Host "    OK 开机重建任务: 系统启动时自动重建端口转发" -ForegroundColor Green

# 9. 生成管理脚本(manage-gateway.ps1)
#    status / restart / health / logs / test 五个子命令,落地到 Scripts 目录

# 输出部署结果
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[$GatewayName] 部署完成" -ForegroundColor Green
Write-Host "----------------------------------------" -ForegroundColor Gray
Write-Host "Tailscale IP : $tsIp" -ForegroundColor White
Write-Host "访问方式     :" -ForegroundColor White
foreach ($nvr in $NvrList) {
    Write-Host "  http://${tsIp}:$($nvr.LocalPort)  ->  $($nvr.NvrIp):$($nvr.NvrPort) ($($nvr.SvcName))" -ForegroundColor White
}
Write-Host "----------------------------------------" -ForegroundColor Gray
Write-Host "安装目录     : $InstallDir" -ForegroundColor White
Write-Host "配置文件     : $ConfigFile" -ForegroundColor White
Write-Host "日志目录     : $LogDir" -ForegroundColor White
Write-Host "健康检查     : 每 5 分钟自动执行" -ForegroundColor White
Write-Host "开机自启     : 系统启动时自动重建端口转发" -ForegroundColor White
Write-Host "========================================" -ForegroundColor Cyan
1. 保存脚本 → 2. 管理员 PowerShell → 3. 执行
# 设置执行策略(首次运行需要)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# 执行部署脚本(根据仓库修改参数)
.\setup-gateway.ps1 -GatewayName "warehouse-a" -NvrConfig "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp"

# 参数说明
#   -GatewayName       工控机在 Tailscale 网络中的主机名
#   -TailscaleAuthKey  可选,填入后免交互自动登录
#   -NvrConfig         格式 "本地端口,NVR内网IP,NVR端口,服务名;..."
manage-gateway.ps1 · 运维五件套
cd C:\NVR-Gateway\Scripts

.\manage-gateway.ps1 status    # 查看端口转发和 Tailscale 状态
.\manage-gateway.ps1 restart   # 重建所有端口转发规则
.\manage-gateway.ps1 health    # 手动执行健康检查
.\manage-gateway.ps1 logs      # 查看最近 50 行日志
.\manage-gateway.ps1 test      # 测试所有端口连通性

# 实时追踪日志
Get-Content C:\NVR-Gateway\Logs\health-check.log -Wait -Tail 20

# 查看当前端口转发规则
netsh interface portproxy show all
deploy-all.ps1
# 批量部署多个仓库
$warehouses = @(
    @{ Name = "warehouse-a"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" },
    @{ Name = "warehouse-b"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" },
    @{ Name = "warehouse-c"; NvrConfig = "8000,192.168.1.100,80,nvr1-web;8554,192.168.1.100,554,nvr1-rtsp" }
)

foreach ($wh in $warehouses) {
    Write-Host "正在部署 $($wh.Name)..." -ForegroundColor Cyan
    .\setup-gateway.ps1 -GatewayName $wh.Name -NvrConfig $wh.NvrConfig
}
C:\NVR-Gateway\ ├── Logs\ │ └── health-check.log # 健康检查记录 ├── Scripts\ │ ├── health-check.ps1 # 定时巡检(每 5 分钟) │ ├── boot-setup.ps1 # 开机重建转发 │ └── manage-gateway.ps1 # 运维管理入口 └── config.json # 集中配置
config.json
{
    "GatewayName": "warehouse-a",
    "NvrList": [
        { "LocalPort": 8000, "NvrIp": "192.168.1.100", "NvrPort": 80,  "SvcName": "nvr1-web"  },
        { "LocalPort": 8554, "NvrIp": "192.168.1.100", "NvrPort": 554, "SvcName": "nvr1-rtsp" }
    ],
    "InstallDir": "C:\NVR-Gateway",
    "LogDir": "C:\NVR-Gateway\Logs"
}
改配置

编辑 C:\NVR-Gateway\config.json 后, 执行 .\manage-gateway.ps1 restart 即可生效,无需重跑部署脚本。

06

持久化与保活设计

netsh 规则重启会丢失 —— 四层机制保障自愈
开机自启

Windows 任务计划程序(AtStartup 触发器),系统启动时自动重建端口转发。

Trigger: AtStartup
定时巡检

每 5 分钟检测 NVR 可达性,规则丢失时自动重新创建。

Interval: 5 min
网络恢复

Tailscale 客户端自带重连,网络恢复后自动重新加入虚拟网。

Auto Reconnect
防火墙持久化

New-NetFirewallRule 写入系统防火墙策略库,重启后依然生效。

Policy Store

双层健康检查

定时巡检 + 开机重建,互为兜底

定时巡检

任务计划程序每 5 分钟触发。通过 PowerShell TcpClient 检测 NVR 端口可达性;不可达时记录日志并重建转发规则,日志写入 health-check.log。

开机重建

系统启动时清除可能残留的旧规则,重新创建全部转发规则,确保重启后服务立即可用。

日志管理

保留最近 30 天 · 建议加入月度计划任务

日志文件内容
C:\NVR-Gateway\Logs\health-check.log健康检查记录
cleanup-logs.ps1(可选)
# 保留最近 30 天日志
$logDir = "C:\NVR-Gateway\Logs"
Get-ChildItem $logDir -Filter "*.log" |
  Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
  Remove-Item -Force
07

访问端配置与地址生成

笔记本 / 手机 · 零特殊配置 · 生成可直接点击的访问地址

IP 可在 Tailscale 控制台查看,或在工控机上执行 tailscale ip -4 获取。输入后右侧地址实时生成。

安装并登录

安装 Tailscale 客户端,登录与工控机相同的 tailnet。

保持连接

确保客户端处于连接状态(系统托盘图标为实心)。

浏览器直连

Web 界面直接用浏览器打开;RTSP 流用 VLC 等播放器打开 rtsp://IP:8554。

WEB
RTSP
WEB2
RTSP2
SSH
08

安全策略 · ACL 配置

Tailscale Admin Console · 只允许管理员访问 NVR 端口
ACL 策略 · Tailscale Admin → Access Controls
{
  "groups": {
    "group:admin": ["[email protected]", "[email protected]"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["group:admin"],
      "dst": ["tag:gateway:8000,8554"]
    }
  ],
  "tagOwners": {
    "tag:gateway": ["[email protected]"]
  }
}

策略说明

最小授权

只有 group:admin 中的用户能访问工控机的 8000 / 8554 端口。

横向隔离

其他 tailnet 成员无法访问仓库内网任何资源。

Tag 打标

如需给工控机打 tag,在 Tailscale 客户端设置中配置;免费版支持最多 50 台标记资源。

双控

安全边界 = Tailscale ACL(谁能进虚拟网)+ Windows 防火墙(哪个端口能进)双重控制。

09

故障排查速查

点击展开排查命令与可能原因
10

落地执行清单

勾选状态本地保存 · 刷新不丢失

A · 部署前

确认工控机为 Win10 专业版 / 企业版 / IoT 企业版
确认 iphlpsvc(IP Helper)服务已启用
下载并安装 Tailscale,准备 authkey 或登录账号
记录各仓库 NVR 内网 IP(如 192.168.1.100)
执行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

B · 部署后验证

netsh interface portproxy show all 能看到全部转发规则
tailscale status 显示工控机在线
浏览器打开 http://<TS-IP>:8000 能进 NVR Web
VLC 打开 rtsp://<TS-IP>:8554 能看到画面
重启工控机一次,确认转发规则自动恢复
0 / 10
11

方案总结

设计选择一览