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

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

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

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

还可以直接使用 Node.js 的 Wol 包,这样就可以不需要另外安装 waleonlan,不过都一样需要使用 root 运行 Node.js 才有权限访问网卡的!

安装和配置环境

我们先安装需要用的工具。或者直接用 node.js 的 wol 包就不需要安装这个。

sudo apt-get install nginx wokeonlan wget -y

如果你用非 root 用户运行的时候会有网卡访问权限问题,发不出魔术封包。就要给 wokeonlan 权限

主要 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 wol silly-datetime --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;

使用 Nodejs 的 wol 包就用下面的代码

/**
 * 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');

// WOL 模块
var wolfun = require('wol');

// 时间模块
var sd = require('silly-datetime');
var nowTime=sd.format(new Date(), 'YYYY-MM-DD HH:mm:ss');

// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false });

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

router.post("*", urlencodedParser, function (request, response) {
	//
	if (!request.body) {
		response.send("请提交设备 MAC 地址");
	} else {
		var thisMAC = request.body.mac;
		wolfun.wake(thisMAC, function(error, postText){
			if (error !== null) {
				console.log('-wol- exec error: ' + error+' '+nowTime);
				response.send('exec error: ' +error);
			} else {
				console.log("-wol- "+thisMAC+' '+postText+' '+nowTime);
				response.send('已经叫咗电脑开机!');
			}
		});
		  
	}
});

module.exports = router;

运行程序

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

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

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

最好还是新建一个 pm2 的配置文件 pm2_config.json,可以设置很多参数。例如下面的文件,可以设置程序目录、应用名称、日志目录等。

{
    "apps" : [{
        "name"        : "nodewol",
        "script"      : "/home/pi/node/www/index.js",
        "cwd"		 :	"/home/pi/node/",
	    "instances"  : "1",
	    "log_date_format"  : "YYYY-MM-DD HH:mm Z",
	    "log_file"   : "/home/pi/node/log/wol.log",
	    "error_file" : "/home/pi/node/log/wol-err.log",
	    "out_file"   : "/home/pi/node/log/wol-out.log",
        "watch"      : true
    }]
}

然后运行等命令就运行这个 json 文件。

pm2 start /home/pi/node/www/pm2_config.json

设置外网访问

设置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,然后直接就可以语音开电脑了。