Google+ Followers

2018年7月1日 星期日

[NAS] Private IP Server 與 Let's Encrypt Wildcard SSL 憑證 / HTTPS 服務

家裡擺了一台 Synology NAS 躲在防火牆後面,一直都是用 private ip 在服務的,以前 Let's Encrypt 有免費的 SSL 憑證服務,但驗證方式需要讓對方連到 server ,一直無法在 private ip server 啟用,可惜了點!然而,Let's Encrypt 開放 Wildcard SSL 憑證服務,採用 DNS TXT Record 驗證,這時就可以上場了!

我的 NAS 是幾年前買的 Synology DS216play ,他的 CPU 挺麻煩的,讓我額外安裝軟體都很不便利,原先想採用 certbot 來做事,安裝了 python3、pip 和 virtualenv 後,最後還是卡關,卡在安裝軟體還需要 compiler 來編譯,擺爛三個月後,我改用了 https://github.com/Neilpang/acme.sh

acme.sh 的使用很方便:

$ wget https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh
$ chmod a+x acme.sh
$ ~/acme.sh --issue -d *.changyy.org --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please


如此後續再添加 TXT record 後,在執行

$ ~/acme.sh --renew -d *.changyy.org --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please

就搞定了!剩下的步驟只是覆蓋憑證檔案跟重啟 web server :

$ grep certificate /etc/nginx/nginx.conf
    ssl_certificate           /usr/syno/etc/certificate/system/default/fullchain.pem;
    ssl_certificate_key       /usr/syno/etc/certificate/system/default/privkey.pem;
$ ls /usr/syno/etc/certificate/system/default/
cert.pem  chain.pem  fullchain.pem  privkey.pem


最後,我把 DNS 擺在 cloudflare 託管,還可以用 API 去處理 DNS TXT Record 更新,連續動作如下:

$ ls ~/.acme.sh/
$ wget https://raw.githubusercontent.com/Neilpang/acme.sh/master/dnsapi/dns_cf.sh -O ~/.acme.sh/dns_cf.sh
$ chmod 755 ~/.acme.sh/dns_cf.sh
$ CF_Key=YOUR_API_KEY_AT_PROFILE CF_Email=YOUR_CLOUDFLARE_EMAIL_ACCOUNT ~/acme.sh --issue --dns dns_cf -d *.changyy.org
$ sudo cp ~/.acme.sh/\*.changyy.org/\*.changyy.org.cer /usr/syno/etc/certificate/system/default/cert.pem
$ sudo cp ~/.acme.sh/\*.changyy.org/\*.changyy.org.key /usr/syno/etc/certificate/system/default/privkey.pem
$ sudo cp ~/.acme.sh/\*.changyy.org/ca.cer /usr/syno/etc/certificate/system/default/chain.pem
$ sudo cp ~/.acme.sh/\*.changyy.org/fullchain.cer /usr/syno/etc/certificate/system/default/fullchain.pem
$ sudo synoservicecfg --restart nginx


如此一來,就可以安排 crontab 用 root 每月跑一次了,另一個好處是可以在 NAS 執行完,再把憑證丟到一些 VPS 維運的機器上

# cat /root/lets-ssl-renew.sh
#!/bin/sh
CF_Key=YOUR_API_KEY_AT_PROFILE CF_Email=YOUR_CLOUDFLARE_EMAIL_ACCOUNT /root/acme.sh --issue --dns dns_cf -d *.changyy.org  && cp /root/.acme.sh/\*.changyy.org/\*.changyy.org.cer /usr/syno/etc/certificate/system/default/cert.pem && cp /root/.acme.sh/\*.changyy.org/\*.changyy.org.key /usr/syno/etc/certificate/system/default/privkey.pem && cp /root/.acme.sh/\*.changyy.org/ca.cer /usr/syno/etc/certificate/system/default/chain.pem && cp /root/.acme.sh/\*.changyy.org/fullchain.cer /usr/syno/etc/certificate/system/default/fullchain.pem && synoservicecfg --restart nginx && echo "SSL-Renew-Done" > /root/ssl-renew.log || echo "SSL-Renew-Failed" > /root/ssl-renew.log
echo date >> /root/ssl-renew.log

2018年6月27日 星期三

親愛的奇利,謝謝你來到我們家

20090124 - 撒嬌

幾天前在辦公室得知奇利過逝,遲疑著幾分鐘後,就請個特休搭高鐵返鄉。一路上淚也止不下來,沒想到早上加油時送的面紙派上了用場。和奇利的緣分近 13 年吧!在我們家處境最糟的時候,加入的成員。

還記得剛來我們家時,傲氣的奇利還不准人摸他的頭,每次要摸就立刻轉身,隨著彼此的生活步調同步著,我教會奇利如何上下樓梯,奇利甚至就學會了不在家裡亂尿尿,忍不住時就在廁所方便,實在貼心。

回顧起來,奇利過得也不錯,只有一開始一兩年住在籠子裡,後來家裡環境比較乾淨後,就不在需要籠子,可以隨處挑喜歡的地方睡覺,甚至跟我們一起吹冷氣,接著陪著我們到處輕旅行,追過每一年的燈會、國際上知名的黃色小鴨以及不少台灣老少咸宜的景點,或是來北部待過我租的每一間房間,這種情誼不是寵物兩字可道盡,只有家人可以詮釋情誼。

近一年奇利健康老化,來台北時,不知是不是長途關係,走著走著就倒下了!而後才知道,他的生命到了末曲,老化帶來的心臟病,心臟瓣膜脫垂會讓他喘,吸不到空氣,開始吃藥、開始緩慢的步調。

回顧起來,我們是不是用著藥物強制延續著緣分?安樂死真的是個課題。但人竟然可以這樣決定別人的生命?
定期進入全氧室能得到紓解、每天早晚的藥物控制可以穩定作息,卻都換不回那可以快樂奔跑的奇利。

喜歡那爽朗的笑容,這樣癡癡的過生活。
非常感謝和奇利的緣分。

他可能只是你的一個寵物,但你卻是他的全部。
越多的互動,真的越難釋懷,只能朝著,奇利,你終於不用在吃藥了,可以開心自在的到處散步

謝謝你來到我們家


回矇一笑

2018年6月2日 星期六

[Python] Line Chatbot 開發筆記 - Echo Service @ Ubuntu 18.04

LineChatbot01

入口:https://developers.line.me/en/
文件:https://developers.line.me/en/docs/messaging-api/overview/

登入開發者後,先來個 Add new provider ,此例為 StudyLineChatbot ,接著選擇 Messaging API,填寫完基本資料後,再按幾下就創建了

其中,重要的資訊:
  • Channel secret
  • Channel access token (long-lived)
而 Channel access token (long-lived) 預設為空,要按個 Issue 來產生一組。

接著,Webhook URL 要填寫對接的 API 位置,該 API 必須提供 https 服務,另外再把 Auto-reply message 關閉。如此一來就完成 Line Chatbot 的設定。

接著談談 Webhook URL 的實作方式,此例用 Python 的 Flask 和 line-bot-sdk-python 完工,簡短程式 hello.py 如下(其實就是 https://github.com/line/line-bot-sdk-python README 範例程式,多增加一點輸出訊息):

from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

line_bot_api = LineBotApi('YOUR_LINE_CHATBOT Channel access token (long-lived)')
handler = WebhookHandler('YOUR_LINE_CHATBOT Channel secret')

@app.route("/", methods=['POST'])
def callback():
    print("[INFO] Get request:")
    print(request.__dict__)
    print()

    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']
    # get request body as text
    body = request.get_data(as_text=True)
    print("[INFO] Request Body: ---\n"+body+"\n---\n")

    # handle webhook body
    try:
        events = handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    print("[INFO] Handle Events \n")
    for event in events:
        if event is None:
            continue
        if not isinstance(event, MessageEvent):
            continue
        if not isinstance(event.message, TextMessage):
            continue
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=event.message.text)
        )
    return '{}'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text)
    )

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)


然後,選擇在 Ubuntu server 上,透過 nginx 跟 uWSGI 設定。

Nginx:

location / {

try_files $uri @chatbot;
}

location @chatbot {
include uwsgi_params;
uwsgi_pass unix:/tmp/chatbot-uwsgi.sock;
}


uWSGI:

$ vim /etc/systemd/system/chatbot.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --ini /path/project/chatbot-uwsgi.ini
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

$ vim /path/project/chatbot-uwsgi.ini
[uwsgi]
base = /path/project/

app = hello
module = %(app)
home = %(base)/venv
pythonpath = %(base)
callable = app

socket = /tmp/%n.sock
chmod-socket = 666
logto = /var/log/uwsgi/%n.log


後續都統一靠以下招數更新:

$ sudo systemctl restart chatbot.uwsgi

以及 hello.py 輸出的資料和運行結果,都可以在 /var/log/uwsgi/chatbot-uwsgi.log 找到:

[INFO] Get request:
{'environ': {'QUERY_STRING': '', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'application/json;charset=UTF-8', 'CONTENT_LENGTH': '237', 'REQUEST_URI': '/', 'PATH_INFO': '/', 'DOCUMENT_ROOT': '/etc/nginx/html', 'SERVER_PROTOCOL': 'HTTP/1.1', 'REQUEST_SCHEME': 'https', 'HTTPS': 'on', 'REMOTE_ADDR': '###.###.###.###', 'REMOTE_PORT': '34994', 'SERVER_PORT': '443', 'SERVER_NAME': 'chatbot.example.com', 'HTTP_X_LINE_SIGNATURE': '#####################', 'HTTP_CONTENT_TYPE': 'application/json;charset=UTF-8', 'HTTP_CONTENT_LENGTH': '237', 'HTTP_HOST': 'chatbot.example.com', 'HTTP_ACCEPT': '*/*', 'HTTP_USER_AGENT': 'LineBotWebhook/1.0', 'wsgi.input': <uwsgi._Input object at 0x7f0d8a4477c8>, 'wsgi.file_wrapper': <built-in function uwsgi_sendfile>, 'wsgi.version': (1, 0), 'wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>, 'wsgi.run_once': False, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.url_scheme': 'https', 'uwsgi.version': b'2.0.17', 'uwsgi.node': b'###########', 'werkzeug.request': <Request 'https://chatbot.example.com/' [POST]>}, 'shallow': False, 'view_args': {}, 'url_rule': <Rule '/' (POST, OPTIONS) -> callback>, 'url': 'https://chatbot.example.com/'}

[INFO] Request Body: ---
{"events":[{"type":"message","replyToken":"###############","source":{"userId":"##############","type":"user"},"timestamp":###########,"message":{"type":"text","id":"###########","text":"HelloWorld"}}]}
---

2018年5月14日 星期一

iOS App 開發筆記 - 關於 case sensitive 檔案系統與 CocoaPods 和 Ruby/Gems 的處理

某天重灌 macOS 時,把主硬碟弄成 case sensitive ,想說自己都很熟悉 linux server 的行為,殊不知替自己埋了很多坑。首先,如果用 CocoaPods 維護套件時,別人的程式碼不見得會依照此規範,同理在 Ruby/Gems 更是如此。

解法?先切一個 partition 不區分大小寫吧!在把 Xcode build code 的暫存區也移至該 partition 來解!

未來還是推薦用 case insensitive 的 partition 吧 Orz 想搞龜毛還是用另一區,千萬別要跟青春過意不去啊。

將 Xcode build 臨時區移走:

$ mv ~/Library/Developer/Xcode/DerivedData/ ~/Library/Developer/Xcode/DerivedData-bak/
$ mkdir /Volumes/Data/XcodeDerivedData
$ ln -s /Volumes/Data/XcodeDerivedData ~/Library/Developer/Xcode/DerivedData


如此一來,關於 Xcode 與 CocoaPods 的大小寫問題應當可以解了,但...如果 build code 過程還有搭配 ruby script 做事,很抱歉,請自己改 ruby 了 Orz 例如:

require 'CFPropertyList'

很抱歉,在 Ruby 的 CFPropertyList 套件中,他實際是小寫的 Orz 請看:

https://github.com/ckruse/CFPropertyList/tree/master/lib

最重要的,這類還可能搞壞 gem 的管理狀態,這時解法就有點痛苦了,絕對不是三兩句就解掉的。

2018年5月9日 星期三

Alexa Skill 開發筆記 - 使用 AWS Lambda 與 OAUTH2 帳號連結

公司的產品是 WiFi Display 設備,結合智慧音箱後,透過音控請裝置執行動作,如播放影片。在此就順便紀錄這些開發過程。此處不會提及 OATUH2 開發項目。

首先,Alexa Skill 是個滿好玩的點子,說穿了就像 Apple TV 可以安裝 Apps 的概念,他是個市集,可以讓開發者提供更多智慧音箱的技能擴充,大約是 2015 年開始的。上頭也可以有簡單的變現機制,可以參考:Alexa開發者也可以賺錢了!看亞馬遜怎麼開啟語音應用的全新獲利模式

首先,沒有 Alexa device 也可以開發,善用模擬器即可。開發流程:

  1. 建立 Alexa Developer 帳號(過程就是建立 Amazon 帳號)
  2. 建立 AWS 帳號(綁定信用卡,有免費額度可善用)
  3. 使用模擬器服務 - https://echosim.io/


01-skill01

02-skill02

先在 Alexa Developer 先建立一個 Skill project,此例是 Video ,接著就要填寫 AWS Lambda 的位置,就切換到 AWS Lambda 創建流程,而 Alexa Skill 預設是提供 English (US) 的語系,參照 To create a Lambda function 第三步有強調 AWS Region 的部分:
Make sure you’ve selected the N.Virginia for English (US) skills or the EU (Ireland) region for English (UK) and German skills. The region is displayed in the upper right corner. Providing your Lambda function in the correct region prevents latency issues.
就要在 N.Virginia 創建 AWS Lambda function,不然亂建立根本無法互動。建立完 AWS Lambda function 後,替他增加 Alexa Skill Kit 和 Alexa Smart Home ,並且設定它的 Skill ID,並且把對應的程式碼上傳,且要記得發布出去!

03-aws01

04-aws02

05-aws03

如此一來,此 Skill 作者基本上已經可以測試了,而測試的條件包括要有一個 Alexa 音控裝置,這時就要靠模擬器,可以先到 https://echosim.io/ 登入 Amazon 帳號,即可獲得一台虛擬器。

15-simulator

接著,若你人在美國或是擁有美國的 iTunes / Google play 帳號可以下載得到 Amazon Alexa app ,那就直接用,不然只好跟我一樣用用網頁版: https://alexa.amazon.com/spa/index.html ,只要有先把 Amazon 帳號配對過 Alexa 音箱就可以正常切換到 Skill 頁面,點選右上角的 Your skill 即可切換到 Dev skill 來查看,並把自己開發的 skill 給 enable,過程就會觸發 OAUTH2 服務帳號的綁定並授權 Skill 服務使用等等,後續的過程先挑選哪個硬體裝置要被智慧音箱控制(AWS Lambda 要實作 device discovery 等機制),接著再挑哪個智慧音箱來搭配,整個過程用瀏覽器開發工具查看到 api response:

  • https://alexa.amazon.com/api/phoenix/discovery
    • 得知哪些硬體裝置要被控制
  • https://alexa.amazon.com/api/devices-v2/device
    • 得知你的帳號有哪些智慧音箱裝置


06-skill03

07-skill04

08-skill05

09-skill06

10-skill07

11-skill08

12-skill09

最後,自己測試完後,可以再開啟 Beta Test 機制,邀請其他人來測試。而要開啟 Beta Test 只需要把資料填一填,並處於 ready for submission 即可(送審前一步),關鍵的地方就是補一補 icon、描述、甚至一些網址等等,若只要一直內測就可以先亂填

13-skill10

14-skill11

最後,有問題真的要好好看文件 XD 而 AWS Lambda 則可以善用 Cloud Watch 看 logs ,以此確保真的有連線