本文搭建一套基于 NAS 的内网远程管控方案,依托微信小程序实现两大核心功能:
整套系统以 NAS 作为 7×24 小时运行的控制中枢,内部部署 Mosquitto MQTT 消息服务作为通信枢纽,Broker 同步开启 WebSocket 协议用于前端长连接交互,由于mosqutto原生websocket协议兼容问题,使用 Nginx 反向代理mosqutto的websocket服务。整体只需要对外开启一个端口,并使用密码认证。
通过微信小程序,在外网下发控制指令,NAS统一执行两类操作:

listener 8083 172.18.0.1protocol websocketsallow_anonymous falsepassword_file /etc/mosquitto/users.passacl_file /etc/mosquitto/aclfilemax_inflight_bytes 1048576max_inflight_messages 20max_keepalive 300max_packet_size 524288max_queued_bytes 4194304max_qos 2max_queued_messages 1000retain_available truemax_connections 30这种方式配置的websocket协议,在浏览器中无法连接,使用nginx进行一次代理就可以了。
除了使用小程序控制,也可以打包成h5页面,部署到nginx中,这样通过浏览器也可以进行操作。小程序中可连接的websocket地址,需要到小程序管理后台配置,不如h5页面自由灵活。
server { listen 18443 ssl; server_name mqtt.sskxyz.top; ssl_certificate_key /etc/nginx/certs/privkey.pem; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; ssl_session_cache shared:SSL:512k; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; ssl_session_timeout 5m; # 代理mosquitto websocket协议 location = /ws { proxy_pass http://172.18.0.1:8083; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_connect_timeout 30s; proxy_buffering off; } # h5页面部署 location /wxwork/ { root /opt/websites/web; try_files $uri $uri/index.html /wxwork/index.html; } # 兼容jsonp的公网地址获取配置 location /ipaddr { default_type application/javascript; if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET,OPTIONS; return 204; } # 没有callback就默认json格式 if ($arg_callback = "") { return 200 '{"ip":"$remote_addr"}'; } # 有callback = 返回标准JSONP格式 return 200 "$arg_callback({\"ip\":\"$remote_addr\"});"; }}// 防火墙修改指令{"action": "add/remove/reset","ip_addr": "114.xx.xx.xx"}// 电脑定时休眠指令{"action": "sleep","duration": 60}防火墙控制:
import net from 'net'import type { IService } from './service';import type { Action } from '../models/model';import { executeShellCommand, Sqlite } from '../utils'const insertStmt = 'INSERT INTO firewall_rules (ip_addr, action) VALUES (?, ?)';const updateStmt = 'UPDATE firewall_rules SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?';export class NftablesService implements IService { support(context: Action): boolean { return ['add', 'remove', 'reset'].includes(context.action) } execute(context: Action): Promise<string> { let { action, ip_addr } = { ...context } if (!net.isIP(ip_addr!)) { console.error(`[-] 安全拦截: 无效的 IP 地址格式 "${ip_addr}"`); return Promise.reject(`无效的 IP 地址格式 ${ip_addr}`) } let nftCommand = this.command(action, ip_addr!) const id: number | bigint = Sqlite.insert(insertStmt, ip_addr, action) return executeShellCommand(nftCommand).then(resp => { Sqlite.update(updateStmt, 'success', id) return resp; }).catch(err => { console.error(`[CMD Error] 命令执行失败: ${err}`); Sqlite.update(updateStmt, id, 'failed') return err; }) } command(action: string, ip_addr: string): string { let nftCommand = 'nft list ruleset ' switch (action) { case 'add': nftCommand = `nft add element inet filter allowed_dynamic { ${ip_addr} }`; break; case 'remove': nftCommand = `nft delete element inet filter allowed_dynamic { ${ip_addr} }`; break; case 'reset': nftCommand = 'nft -f /etc/nftables.conf' break; } return nftCommand; }}休眠控制:
import { Action } from "../models/model";import { IService } from "./service";import { executeShellCommand } from "../utils";export class SuspendService implements IService { support(context: Action): boolean { return context.action === 'sleep' } execute(context: Action): Promise<string> { let { duration } = { ...context } let wake_time = Math.floor(Date.now() / 1000) + duration! * 60; let nftCommand = `rtcwake -t ${wake_time} -vm mem` return executeShellCommand(nftCommand).then(res => { console.info("- 执行休眠成功") return `执行休眠成功: ${res}` }).catch(err => { console.error(`- 执行休眠失败: ${err}`) return `执行休眠成功: ${err}` }) }}H5示例
小程序示例:

以上内容解决了白天nas访问、休眠控制的问题,如果希望晚上11点到早上8点,nas自动休眠,可以配置一个定时脚本,之前有介绍过,这里再贴一下:
休眠控制脚本:
#!/bin/bashfunctiondisable() {#sed -i 's/AllowSuspend=.*/AllowSuspend=no/' /etc/systemd/sleep.confprintf'%s\n'',s/AllowSuspend=.*/AllowSuspend=no/g' w q | ed -s /etc/systemd/sleep.conf}functionenable() {#sed -i 's/AllowSuspend=.*/AllowSuspend=yes/' /etc/systemd/sleep.confprintf'%s\n'',s/AllowSuspend=.*/AllowSuspend=yes/g' w q | ed -s /etc/systemd/sleep.conf}functionstatus() { yes_no=$(sed -n '/AllowSuspend=/p' /etc/systemd/sleep.conf | cut -d '=' -f2)echo$yes_no}curr_time=$(date +%H%M)wake_time=$(date -d "next day 08:05" +%s)stat=$(status)if [ $curr_time -gt 2230 -o $curr_time -lt 0800 ];thenif [ "no" = $stat ];thenenablefi rtcwake -t $wake_time -vm memelseif [ "yes" = $stat ];thendisablefifisystemd定时器:
root@fnos:~# systemctl cat sched_sleep.service# /lib/systemd/system/sched_sleep.service[Unit]Description=Schedule Sleep FNOS[Service]Type=oneshotUser=rootExecStart=/root/sleep.sh[Install]WantedBy=multi-user.target# 定时配置,每10分钟执行一次root@fnos:~# systemctl cat sched_sleep.timer# /lib/systemd/system/sched_sleep.timer[Unit]Description=Schedule Sleep FNOS[Timer]OnCalendar=*:0/5RandomizedDelaySec=10mPersistent=true[Install]WantedBy=timers.target防火墙参考配置: 仅允许18443这一个端口对外可访问,供小程序和h5连接,将来源IP地址通过小程序加入后,才可以访问其它服务,将服务暴露范围最小化。 如果有vpn服务也可以对外暴露,但要配置比较安全的证书和用户名密码。
#!/usr/sbin/nft -fflush rulesettable inet filter {set allowed_dynamic {type ipv4_addr; flags dynamic } chain input {type filter hook input priority filter; policy drop ip6 nexthdr icmpv6 counter accept udp dport mdns counter accept ct state related,established counter accept iifname "lo" accept iifname "enp0s31f6-ovs" udp dport { 53, 67 } accept iifname "enp0s31f6-ovs" tcp dport { 53, 67 } accept ip saddr { 192.168.5.0/24, 10.8.0.0/24, 172.18.0.0/24 } counter accept tcp dport { 61194, 8883 } counter accept ip saddr @allowed_dynamic tcp dport { 5667,60022 } counter accept } chain forward {type filter hook forward priority filter; policy drop ct state related, established counter accept ip daddr 172.18.0.2 tcp dport 18443 counter accept ip saddr @allowed_dynamic iifname "enp0s31f6-ovs" counter accept ip saddr 192.168.5.0/24 counter accept iifname { "tun0", "br-691295fe2d05" } counter accept } chain output {type filter hook output priority filter; policy accept }}table inet nat { chain postrouting {type nat hook postrouting priority srcnat ip saddr 10.8.0.0/24 counter masquerade ip saddr 172.18.0.0/24 counter masquerade } chain prerouting {type nat hook prerouting priority dstnat ip daddr 192.168.5.254 tcp dport 18443 counter dnat to 172.18.0.2:18443 ip daddr 192.168.5.254 tcp dport { 443, 8443 } counter dnat to 172.18.0.2:443 ip daddr 192.168.5.254 tcp dport 3000 counter dnat to 172.18.0.6:3000 ip daddr 192.168.5.254 tcp dport 9090 counter dnat to 172.18.0.8:9090 ip daddr 192.168.5.254 tcp dport 18789 counter dnat to 172.18.0.168:18789 ip daddr 192.168.5.254 tcp dport 18790 counter dnat to 172.18.0.168:18790 ip daddr 192.168.5.254 tcp dport 3001 counter dnat to 172.18.0.20:3000 ip daddr 192.168.5.254 tcp dport 12345 counter dnat to 172.18.0.99:12345 ip daddr 192.168.5.254 tcp dport 54321 counter dnat to 172.18.0.200:5432 }}在做了上面这些之后,我就把安全中心这个应用卸载掉了。