はじめに
前回の続きです.
とりあえずやりたいことはこんなんです.
今回は遂に「Wake on LAN用のPythonスクリプトを作成する」です.
- [1]GCPとMyDNSを使って,ドメインを取得する
- [2]GCPにSoftether Serverを立てる(iPhone/ラズパイから接続)
- [3]GCP上のApacheからローカルのラズパイApacheにリバースプロキシする
- [4]ラズパイをルーター化する
- [5]Wake on LAN用のPythonスクリプトを作成する.
DesktopPCの設定
前提:Ubuntu18.04です.
まずはWake on LANが実行できるように設定します.
##固定IPの割り当て
sudo vi /etc/netplan/~~.yaml
以下を記入します,
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
ethernets:
eno1:
dhcp4: no
wakeonlan: true # enable wake on lan
addresses: [192.168.1.**/24] # assign arbitrary address
gateway4: 192.168.1.1 # your router(raspi)'s address
nameservers:
addresses: [8.8.8.8,8.8.4.4] # google's public dns
再起動します.
sudo netplan apply
sudo reboot
wake on lan が有効になっているか確認するためにethtool
をインストールします.
sudo apt-get install -y ethtool
Ethernetの名前をメモします.ついでにIPが固定されているか確認します.
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eno1<<<<copy<<<<: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
inet 192.168.1.**/24 brd 192.168.1.255 scope global noprefixroute eno1
valid_lft forever preferred_lft forever
inet6 fe80::2d8:61ff:fe56:242d/64 scope link
valid_lft forever preferred_lft forever
Wake on LANが有効になっているか確認します.Wake-on: g
になっていればOKです.
$ sudo ethtool eno1
~~
Supports Wake-on: pumbg
Wake-on: g # if it's d, your wake on lan setting may be wrong
~~
BIOSの設定
省略します.起動される側のPCのWOL設定(1/3):BIOS/UEFIの設定wo
参考にしてください.
確認
ラズパイでip a
を確認してDesktopに繋いでいるEthernetにipが割り当てられていればOKです.(UP,LOWER_UP
になっている必要があります.ありがとうtelcomM)
$ ip a
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global noprefixroute eth1
valid_lft forever preferred_lft forever
inet6 fe80::83fa:6dee:9799:9a6e/64 scope link
valid_lft forever preferred_lft forever
確認(wakeonlanをインストールし起動できるか確認)
ラズパイにwakeonlanをインストールします.
sudo apt install wakeonlan
wakeonlan -i 192.168.1.255 -p 7 **:**:**:**:**:**
これで起動できればOKです.
mod_wsgi
まず,PythonスクリプトをApacheで動かすことができるmod_wsgi
と諸々をインストールします.
sudo apt install python3-dev python3-pip apache2 libapache2-mod-wsgi-py3
sudo apt-get -y install python3 ipython3 python3-flask curl
Pythonスクリプト作成
とりあえず動くPythonスクリプトを作成します.今回はFlaskを用います.
cd ~
mkdir flask && cd flask
vi app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'This is vpn server for wake on lan!\n\n'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
とりあえず,mod_wsgiが動くか確認します.
$ python3 app.py
$ curl http://localhost:5000 # 別タブで
This is vpn server for wake on lan!
”This is vpn server for wake on lan!”と出ればOKです.
Apacheとmod_wsgiの設定
新たにflask用の設定ファイルを作成します.
sudo vi /etc/apache2/sites-available/flask.conf
今回はGCPから80ポートにリダイレクトされるようにしているので以下のように記入します.
<VirtualHost *:80>
ServerName kado.example.com
WSGIDaemonProcess flask user={username} group={username} threads=5
WSGIScriptAlias / /home/{username}/flask/adapter.wsgi
<Directory /home/{username}/flask/>
WSGIProcessGroup flask
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Require all granted
</Directory>
</VirtualHost>
flask.conf
を有効化します.
sudo a2dissite 000-default.conf
sudo a2ensite flask.conf
sudo service apache2 restart
adapter.wsgiファイルを作成します.
vi ~/flask/adapter.wsgi
import sys
if sys.version_info[0]<3: # require python3
raise Exception("Python3 required! Current (wrong) version: '%s'" % sys.version_info)
sys.path.insert(0, '/home/kadorpi/flask/')
from app import app as application # <- app means app of app.py
Apacheを再起動します.
sudo service apache2 restart
動いているか確認します.
$ curl localhost
This is vpn server for wake on lan!
これで,PythonスクリプトがApache上で動くようになりました.
Line bot用のWake on LANスクリプト作成
ここまできたら後少しです.LineからGCPにHTTPリクエストを送り,受け取ったリクエストをラズパイの80ポートにリダイレクトします.そして,ラズパイで受け取ったリクエストをPythonで処理します.
まず,Line Developerにアカウントを登録します.
次にChannelを作成します.
Channel Secretを発行し,メモします.
Channel Access Tokenを発行し,メモします.
>Response settingsからAuto-replyなどを適宜設定します.
>Message APIからWebhookURLを入力します.今回はhttps://{domain name}/wake-on-lan
とします.
これでLINE Botの設定は終わりです.
mod_wsgiの再設定
環境変数として,先ほどメモしたChannel SecretとChannel Access Tokenを設定します.
vi ~/flask/adapter.wsgi
以下を追記します.
import os
os.environ['LINE_CHANNEL_SECRET'] = 'fuga'
os.environ['LINE_CHANNEL_ACCESS_TOKEN'] = 'hogehoge'
Pythonスクリプト
必要なモジュールをインストールします.
pip3 install line-bot-sdk flask
pythonコードは以下のようにします.
import os, sys
from flask import Flask, request, abort
app = Flask(__name__)
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
TextMessage, TextSendMessage, MessageEvent
)
# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
@app.route('/')
def show_web():
return 'This is vpn server for wake on lan!\n\n'
@app.route('/wake-on-lan', methods=['POST'])
def wake_on_lan():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'ok'
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
message = event.message.text
replies = []
if re.compile("\s*(check)\s*", re.IGNORECASE).search(message):
result = confirm()
replies += [TextSendMessage(result)]
elif re.compile("\s*(kick)\s*", re.IGNORECASE).search(message):
if confirm() == 'Awake':
replies += [TextSendMessage('Already awake')]
else:
result = kick()
replies += [TextSendMessage(result)]
buttons_template = ButtonsTemplate(
title='usage', text='Tap below buttons', actions=[
MessageAction(label=m, text=m) for m in message_list
])
template_message = TemplateSendMessage(alt_text='Usage', template=buttons_template)
replies += [template_message]
line_bot_api.reply_message(event.reply_token, replies)
def confirm():
hostname = "192.168.1.**"
response = os.system("ping -c 1 " + hostname)
# and then check the response...
if response == 0:
pingstatus = "Awake"
else:
pingstatus = "Sleeping"
return pingstatus
def kick():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.sendto(b'\xFF' * 6 + b'\x**\x**\x**\x**\x**\x**' * 16, ('192.168.1.255', 7))
s.close()
return 'kicked'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
少し説明を入れると,まず以下でLINE用のChannel SecretとChannel Access Tokenを読み込みます.
# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
以下は,Web表示用に一応加えました.
@app.route('/')
def show_web():
return 'This is vpn server for wake on lan!\n\n'
ここに上記で設定したWebhookのリクエスト処理を書きます.
@app.route('/wake-on-lan', methods=['POST'])
def wake_on_lan():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'ok'
そして,最後にメッセージで"kick"が来たら,MagicPacketの送信,"check"が来たらPingで起動できているかを確認する処理をここで書いています.
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
message = event.message.text
replies = []
if re.compile("\s*(check)\s*", re.IGNORECASE).search(message):
result = confirm()
replies += [TextSendMessage(result)]
elif re.compile("\s*(kick)\s*", re.IGNORECASE).search(message):
if confirm() == 'Awake':
replies += [TextSendMessage('Already awake')]
else:
result = kick()
replies += [TextSendMessage(result)]
buttons_template = ButtonsTemplate(
title='usage', text='Tap below buttons', actions=[
MessageAction(label=m, text=m) for m in message_list
])
template_message = TemplateSendMessage(alt_text='Usage', template=buttons_template)
replies += [template_message]
line_bot_api.reply_message(event.reply_token, replies)
def confirm():
hostname = "192.168.1.**"
response = os.system("ping -c 1 " + hostname)
# and then check the response...
if response == 0:
pingstatus = "Awake"
else:
pingstatus = "Sleeping"
return pingstatus
def kick():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.sendto(b'\xFF' * 6 + b'\x**\x**\x**\x**\x**\x**' * 16, ('192.168.1.255', 7))
s.close()
return 'kicked'
Apacheを再起動します.
sudo service apache2 restart
おわり
後半,疲れてだれてしまいました...
さらに備忘録的に書いているのでわかりづらいかもしれません...
とりあえず全部書けたので良かったです。参考になれば幸いです.
参考
How To Use Apache HTTP Server As Reverse-Proxy Using mod_proxy Extension