使用树莓派实现外网唤醒内网设备

现在还没有买 NAS,文件都放在电脑里,在外面要用就得先远程开机然后连接家里电脑拿文件。这时候就要通过树莓派来唤醒电脑了。

主要功能就是接收外部请求,然后发送魔术封包唤醒内网设备。通过 nginx 反代 Node.js,接收到外部发送的内网设备的 mac 然后发送魔术封包。当然需要你的宽带有公网 IP 才可以,而且你需要有自己的域名,或者路由器自己有提供 DDNS 域名的,例如华硕的路由就提供域名给用户设置 DDNS 的。

首先是你要在电脑的网卡设置允许外部唤醒,同时 Bios 里也要开启允许网络唤醒。

命令工具用 wakeonlan,需要安装 sudo apt-get install wakeonlan ,某些系统是 wakelan

安装和配置环境

我们先安装需要用的工具。

sudo apt-get install nginx wokeonlan wget -y

主要 Node.js 则需要下载解压再安装的。

# 先看一下系统内核
uname -a

# 因为树莓派 Zero W 是 armv6l 所以下载时候要注意你的 CPU。
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-armv6l.tar.xz

# 解压
xz -d node-v10.15.1-linux-armv6l.tar.xz
tar -xavf node-v10.15.1-linux-armv6l.tar

# 把 Node.js 而进驻包移动到系统目录
sudo mv ./node-v10.15.1-linux-armv6l /usr/local/node

# 创建 Node.js 和 npm 的连接
sudo ln -s /usr/local/node/bin/node /usr/bin/node
sudo ln -s /usr/local/node/bin/npm /usr/bin/npm

这样就安装完成了。但是还要注意一样就是把目录加入环境变量!要不然用 npm 安装包只会就用不了的。

# 先看一下 npm 模块目录 例如:/usr/local/node/bin/npm
which npm

# 把返回的目录添加到环境变量
sudo nano /etc/profile

然后在后面加上 PATH=$PATH:/usr/local/node/bin/npm ,保存退出,然后执行 sudo source profile 或者重启树莓派。

确定代码放在哪个文件夹,我是放在用户目录下的,也就是 /home/pi/node/www/

新建文件夹

mkdir node
cd node
mkdir www
cd www

然后在这个文件夹安装 expressbody-parser 两个需要依赖的包。

npm install express body-parser -sava

还有我们要用 pm2 代替直接 node 来后台运行服务,所以还要安装 pm2

sudo npm install pm2 -g

Nginx 设置

Nginx 主要用来处理请求,开启 SSl 等,然后反代本机的 Node.js。
我直接使用 nginxconfig.io 来生成服务器配置。

SSL 证书网上也有很多可以免费的泛域名证书,但是 SSL 就要你自己的域名和证书了,或者你可以用 acme.sh 在树莓派配置 Let’s Encrypt 证书,这样就不怕证书过期了。

Nginx 的详细用途直接网上一搜就一大堆了,不需要我这里介绍了。

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name myhome.boxks.com;

# SSL 证书路径
ssl_certificate /etc/nginx/ssl/myhome.boxks.com.crt;
ssl_certificate_key /etc/nginx/ssl/myhome.boxks.com.key;

# reverse proxy
location / {
# 反代本地 Node.js
proxy_pass http://127.0.0.1:5050;
proxy_http_version  1.1;
proxy_cache_bypass  $http_upgrade;

proxy_set_header Upgrade            $http_upgrade;
proxy_set_header Connection         "upgrade";
proxy_set_header Host               $host;
proxy_set_header X-Real-IP          $remote_addr;
proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-Host   $host;
proxy_set_header X-Forwarded-Port   $server_port;
}

# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# . files
location ~ /\.(?!well-known) {
  deny all;
}

# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
}

# HTTP redirect
server {
  listen 80;
  listen [::]:80;

  server_name myhome.boxks.com;

  return 301 https://myhome.boxks.com$request_uri;
}

Node.js 代码

主文件用 index.js,因为后续要做其他,所以最好就是将 wol 制作成模块。

//1. 导入express
var express = require('express');
var wol = require('./wol.js');

// 创建服务器
var app = new express();

app.use('/wol', wol);

//. 绑定端口
app.listen(5050);

wol.js 文件

/**
* Post请求执行 shell 命令
* 只要向这个地址提交 {"mac":"设备 MAC 地址"}
* kaiyuan Hsie
* https://boxks.com
* */

//1. 导入express
var express = require('express');
// 解析器 需要安装 npm install -g body-parser
var bodyParser = require('body-parser');
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false });

// 创建服务器
var router = express.Router();

router.post("*", urlencodedParser, function (request, response) {
var process = require('child_process');
// Node.js 提供的子进程运行 shell 命令
if (!request.body) {
 response.send("请提交设备 MAC 地址");
} else {
/**
*   使用工具 wakeonlan (需要安装), 某些系统是 wakelan
*   Linux 也可使用命令 ether-wake -i br0 (设备 MAC 地址)
**/
process.exec('wakeonlan '+request.body.mac, function (error, stdout, stderr) {
 if (error !== null) {
    console.log('-wol- exec error: ' + error);//日志加上“-wol-”前缀方便以后搜索
    response.send('exec error: ' +error);
  } else {
    console.log("-wol- "+request.body.mac);
    response.send('成功执行命令');
  }
});
}

})
module.exports = router;

运行程序

pm2--watch 是自动检测文件修改然后重启服务

pm2 start /home/pi/node/www/index.js --watch

pm2 的详细用法可以看 Github 上的说明。

设置外网访问

设置DDNS

我自己使用的是 华硕 AC87U 路由器,路由就自带有 DDNS 功能,所以域名方面可以不用担心的。

我则是用自己的域名和 SSL 证书。

进路由后台,在「外部网络(WAN)」里面选择 DDNS,然后服务器选择华硕的,再填上你的二级域名,「HTTPS/SSL Certificate」选择「Import Your Own Certificate」然后上传你的证书。这里的证书不会影响你树莓派上的,单纯是访问你路由器管理页面用的!

如果你的服务商没有禁止了 80 端口的话其实可以选择「Free Certificate form Let’s Encrypt」就可以自动获取免费的证书了。

设置端口转发

同样是「外部网络(WAN)」顶部选择「端口转发」,然后自己设置一个端口映射到树莓派 443 端口就可以了。

域名 CNAME

我使用的是 CloudFlare,在 DNS 设置中加入 CNAME,然后不要使用 CloudFlare 的 CDN,因为我们需要的是直接连接家里的网络,不需要用 CDN 中转。

手机唤醒电脑

iOS 12 已经有「捷径」这样的工具,可以做很多事情,我就使用这个发送请求到树莓派的。

新建捷径,第一步先「URL」把树莓派的实际网址加进去,然后用「取得 URL 内容」选择 POST 表单,key 设置为 mackey 设置你设备的 MAC,然后最后面用「显示结果」调用「URL内容」就可以了。

你还可以把这个捷径加入 Siri,然后直接就可以语音开电脑了。