Linux Server 伺服器建置筆記 (用 Ubuntu 設定基礎網路 & SSH伺服器)

這邊整理了一些手動 Linux server 伺服器安裝,需注意的一些事情與指令。
備忘一下以備不時之需。
(如果是設定雲端主機的話,部分步驟可以跳過,它預設都幫你建好了。)

製作可開機 USB (Bootable USB)

使用 UNetbootin 軟體

軟體下載:https://unetbootin.github.io/

選擇 USB drive,選擇 ISO 就可以了
針對目標機器做開機。

Mac 系統的話,可以使用 Etcher
軟體下載:https://www.balena.io/etcher/

做法差不多


選擇作業系統:

  • Debian 系列:可選擇 Ubuntu, Debian
  • RedHat 系列:可選擇 RHEL, CentOS, Fedora
  • BSD 系列:可選擇 FreeBSD
  • SUSE 系列:可選擇 OpenSUSE

前二項是筆者較為熟悉的,推薦 Ubuntu, Debian, CentOS 做為選項。

ISO 的版本很多:

  • Desktop ISO:有一個完整的 Live CD 可供試用
  • Server ISO:有預載一些伺服器使用的套件
  • Minimal ISO:只是檔案小,預設網路驅動了之後,大多都從網路上抓

不知怎麼選擇的話,預設就選 Desktop ISO
(以下撰文用 ubuntu 做示範)

網路指令相關

這邊列出常用的網路指令,如果網路不通的事情,當然要優先處理。

列出網路介面與 IP 位址

$ ip a
$ ip addr show
$ ifconfig

這幾個指令都可以,輸出格式稍有不同。

列出路由閘道 Gateway 設定

$ route -n

設定網路連線資訊

這邊介紹一個新東西:netplan
網路對它介紹不多,但個人覺得非常可以取代目前網路設定不方便的窘境。

假設你要設定的網路連線資訊如下:

  • 目標介面: eth0

  • IP 位址 (IP Address): 192.168.10.200

  • 子網路遮罩 (Netmask): 255.255.255.0 (/24)

  • 網路閘道 (Gateway): 192.168.10.1

  • 主要 DNS 為 8.8.8.8

  • 次要 DNS 為 168.95.1.1

(請根據你的自身環境修改,這裡只是舉例)

只要找到 /etc/netplan/01-netcfg.yaml 這個檔案並編輯

$ sudo vi /etc/netplan/01-netcfg.yaml

修改成類似以下內容:

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      addresses: [192.168.10.200/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [8.8.8.8,168.95.1.1]
      dhcp4: no

(請根據你的自身環境修改,這裡只是舉例)

就這樣而已,省二、三個指令,簡單又直覺。

如果你要 dhcp (自動取得 IP 位址) 那更簡單了:

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: yes

然後存檔離開

執行一個很直覺的指令: netplan try

$ sudo netplan try
Do you want to keep these settings?

Press ENTER before the timeout to accept the new configuration
Changes will revert in 120 seconds

是否要保留設定?按 Enter 保留設定,不然 120 秒後會復原。
就跟切換螢幕解析度一樣簡單。

爾或者可以用 netplan apply 直接套用。

$ sudo netplan apply

(舊式) 設定 IP 位址 (IP Address)

$ sudo ip addr add 192.168.10.200/24 dev eth0

這邊用 192.168.10.200 做為例子,請修改成恰當的值。

(舊式) 設定網路閘道 Gateway

$ sudo route add default gw 192.168.10.1 eth0

這邊用 192.168.10.1 做為例子,請修改成恰當的值。

(舊式) 設定 DNS

$ sudo echo nameserver 8.8.8.8″ > /etc/resolv.conf

這邊用 8.8.8.8 的 Google DNS 做為例子,你也可以調整成你喜歡的。

DHCP Relase

釋放從 DHCP 取得的 IP 位址

$ sudo dhclient -r

指令等同 Windows 裡的 ipconfig /release

DHCP Renew

從 DHCP 重新取得新的 IP

$ sudo dhclient

指令等同 Windows 裡的 ipconfig /renew

啟動/關閉 網路介面 (ip 指令)

$ ip link set dev eth0 up
$ ip link set dev eth0 down

例如介面名稱為 eth0,請自行修改成合適的網路名稱。

啟動/關閉 網路介面 (ifconfig 指令)

$ /sbin/ifconfig eth0 up
$ /sbin/ifconfig eth0 down

例如介面名稱為 eth0,請自行修改成合適的網路名稱。

列出所有網路介面與狀態

$ ip link show
$ ifconfig -a

這二個都可以

檢查外部公有 IP (Public IP)

$ curl ipinfo.io/ip

一個簡單的指令可以查詢外部公有IP地址 (Public IP)

SSH 相關

安裝 SSH Server (應該預設就有安裝了)

應該預設就有安裝了,如果沒有安裝,請手動用指令安裝之。
(以下為 ubuntu 的指令)

$ sudo apt install -y ssh openssh-server

開機預設啟動 ssh

$ sudo systemctl enable ssh

啟動 ssh

$ sudo systemctl start ssh

查看 ssh 狀態

$ sudo systemctl status ssh

使用 ssh key 取代密碼登入

增加方便性也加強安全性,建議用 ssh key (pem) 檔案來登入 ssh。

產生 ssh key

$ ssh-keygen

指定檔案,例如 id_rsa 檔案(檔名可自訂)。
密碼 passphrase 可以留空

將會產生 id_rsa (私鑰) 與 id_rsa.pub (公鑰) 檔案。

接下來的步驟將是把您的公鑰複製到伺服器上(或者是把私鑰下載回使用者電腦上)。
使用者(你)透過電腦上的私鑰來做連線。

自動複製 ssh key

(在 Client 端執行此指令)
這個步驟是自動把您的公鑰複製到伺服器上。

$ ssh-copy-id -i ~/.ssh/id_rsa -p 22 [email protected]

如果不能運作也不用太糾結,等等有手動的方式。

運行結果:

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/user/.ssh/id_rsa.pub"
The authenticity of host '[192.168.10.200]:22 ([192.168.10.200]:22)' can't be established.
ECDSA key fingerprint is SHA256:wYmwxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcFme8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:

Number of key(s) added:        1

Now try logging into the machine, with:   "ssh -p '22' '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

另外一個指令,作法相同。

$ cat ~/.ssh/id_rsa.pub | ssh -p 22 [email protected] "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

或者手動複製產生之公鑰 (PublicKey) 到伺服器的 ~/.ssh/authorized_keys 檔案。
(如果沒有 .ssh 隱藏資料夾與 authorized_keys 檔案,請自行建立。)

設定 SSH 關閉密碼登入

$ vi /etc/ssh/sshd_config

找到這行並修改

PasswordAuthentication no

設定免密碼 sudo

(這個步驟非必要)
在設定之前,先調整預設開啟的編輯器。
因為小弟長期習慣用 vim 所以用此指令先切換預設開啟的編輯器

$ sudo update-alternatives --config editor
There are 4 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/ed             -100       manual mode
  2            /bin/nano            40        manual mode
  3            /usr/bin/vim.basic   30        manual mode
  4            /usr/bin/vim.tiny    15        manual mode

Press <enter> to keep the current choice[*], or type selection number: 3
update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/editor (editor) in manual mode

ubuntu 預設是開 nano 編輯器,可以用這個來修改
可以選擇 3 改用 vim 編輯器。

然後使用該指令編輯設定檔

$ sudo visudo

找到

%sudo   ALL=(ALL:ALL) ALL

把它改成

%sudo   ALL=(ALL:ALL) NOPASSWD: ALL

然後存檔離開。

連線 SSH

這個可以做為 bash script 以後方便使用。

$ ssh -i ~/.ssh/id_rsa -p 22 [email protected]

軟體更新

時時軟體更新、修補漏洞、修復 Bug 是很重要的,以下是一些常用的指令

更新套件庫清單(取得有哪些套件已更新)

$ sudo apt update -y

更新套件

$ sudo apt upgrade -y

確認版號

$ lsb_release -a

其實另外二個都可以,個人比較喜歡這個

$ cat /etc/os-release  
$ hostnamectl

列出 Linux 核心版本號

$ uname -r

整個大版本更新
例如從 ubuntu 20.04LTS 升到 ubuntu 22.04.2LTS

$ sudo do-release-upgrade

參考資料

https://www.cyberciti.biz/faq/ubuntu-linux-install-openssh-server/
https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server
https://vitux.com/ubuntu-ip-address-management/
https://www.cyberciti.biz/faq/howto-linux-renew-dhcp-client-ip-address/
https://tldp.org/HOWTO/Linux+IPv6-HOWTO/ch05s02.html
https://www.cyberciti.biz/faq/upgrade-ubuntu-20-04-lts-to-22-04-lts/

[開箱分享] 設定淺色主題 (Light theme) – 感受 文石 Boox Mira 13.3″ 純黑白體驗

繼上一篇 Boox Mira 開箱文,這篇細節來講講如何在您的電腦做一些簡單的設定,
設定白色為背景的主題 (Light theme),讓對比度提高,增加閱讀與寫作的體驗。
這邊用小弟常用的軟體,Sublime text、VS code、Intelij 做介紹。


此系列文:


就讓我們一起來感受 Boox Mira 純黑白體驗吧!

Mira app 本身設定

設定圖

Mira螢幕解析度

分享一下我的設定值:

  • 顯示模式 (Refresh speed):普通 (適合網頁/文字閱讀)
  • 刷新速度(Refresh speed):6
  • 深色增強 (對比度) (Dark Color Enhancement):10
  • 淺色過濾 (Light Color Filiter):50
  • 冷光 (Color light):0 (不開)
  • 暖光(Warm light):0 (不開)
  • 定時自動全刷:120 秒
  • 螢幕解析度:1280 x 960

這邊講一下 Mira 配套的 app 一些能調整的細節

  • 顯示模式
    - 一般模式 (個人推薦)
    - 影片模式 (防止抖動使用)
    - 文字模式 (黑白過濾,只剩黑與白)
    - 圖片模式 (採最高畫質輸出,手動刷新)

刷新模式

這個您可以每個都試試看,
文字模式猜測是關閉灰階演算法,直接一個臨界值 (threshold) 就分成黑與白,理論上對比度最高,但就畫面比較生硬。
圖片模式屬於靜態顯示使用,關閉刷新,採最高畫質輸出,如果要展示餐廳餐單之類的可以選擇。

我個人覺得一般模式最好,如果要看影片,不仿選擇影片模式,減少灰色部分因灰階演算法造成的閃爍。
但影片模式的代價就是比較糊,文字類的就沒那麼清楚。

  • 刷新速度
    字面上的意思,數值越大,反應速度越快,相對的畫質越差,可以調整到滑鼠滑過去覺得順暢的刷新程度
  • 黑色加強
    字面上的意思,數值越大,偏黑色的灰色顯示就會就會黑色,為了增加對比度。
  • 白色過濾
    數值越高,灰色的地方就會被過濾成白色。
  • 冷暖色光調整
    就是調整面板的背光,冷暖雙色可以自由混和,也可以連動,也可以不開,
    像我就是不開這派的。

刷新模式差異

一般模式看文字內容

屬於細節均橫,圖片也不會太誇張的配置。

細節特寫,對比很清楚

文字模式看文字內容

乍看下沒什麼差異,但工具列已經黑糊在一起了。

影片模式看文字內容

細節特寫,實在有點糊

一般模式看圖片

文字模式看圖片

二個字 — 悲劇。
文字模式還是做它擅長的事情就好了。

影片模式看圖片

畫面整個變均衡了

細節特寫,這個石頭細節很棒!

系統設定 (Mac)

因為小弟主要是使用 Mac,
以下用 Mac 的方式跟大家分享。

左上蘋果按鈕 > System Preferences

General > Appearance: Light
使用淺色主題

Accessibility > Display

  • Reduce motion 減少動畫
  • Reduce transparency 減少透明度效果(增加對比度)

這二項可以視情況開啟

Accessibility > Cursor

  • Cursor size 這邊可以調整游標大小(如果有需要的話)

Display > Night Shift: Off
記得不要打開夜覽 (Night Shift) 功能,因為它會調整螢幕顏色,
正白色不會是正白色,而變成灰色,就會霧霧的

常用軟體類

Visual studio code

微軟出品的,也是資訊界大家很推薦好用的編輯器。就算不是資訊工程師,拿來文字處理也是好用。

下載連結:https://code.visualstudio.com/

分享一下它的設定。

在 Code > Preferences > Settings
會開出設定視窗

Workbench > Appearance
Color Theme 改成以下都可以

  • Light+ (default light)
  • Light (Visual Studio)
  • Quiet Light

就會是淺色系的編輯器了。

Sublime Text

小弟常用的一個很實用的編輯器,也是很老牌的編輯器。
就算不是工程師,拿來文字處理也是好用。
時不時會跳出一下使用購買的提示,免費就可以使用它全部的功能,如果喜歡的他們的軟體的話,當然付費支持一下摟!

軟體下載連結:https://www.sublimetext.com/

分享一下它的設定。

在 Sublime Text > Preferences > Select Color Scheme

會出現一個選單,選擇

  • Breakers
  • Celeste
  • Sixteen

這三個都可以,都是淺色主題,差異在語法標示 (Syntax highlighting) 的顏色不同,
但在 Mira 底下看起來應該是接近的(都黑白的嘛 😂 )

在電子紙的部分當然盡可能黑白對比度高,以白底背景為主的介面,會更好

InteliJ IDEA

這個不用多說,在資訊界很有名的付費編緝器之一。
強大的搜尋處理、語意提示、自動完成…等等,講都講不完。

軟體下載連結:https://www.jetbrains.com/idea/

分享一下它的設定。

在 InteliJ IDEA > Preferences

Appearance & Behavior > Appearance

Theme: macOS Light

iTerm2 終端機

這個在 Mac 底下,開放原始碼的終端機,比起內建的終端機,有更多的客製選項可以使用。

軟體下載連結:https://iterm2.com/

分享一下它的設定。

在 iTerm2 > Preferences 的地方

Profile > Colors 的地方,可以自由調整顏色

我們選最快速的 Color Presets,選擇以下二種都可以

  • Light Background
  • Tango Light

Mozilla Firefox 瀏覽器

這個大家就熟悉了,除了 Google chrome,另一個效能與注重隱私的瀏覽器

軟體下載連結:https://www.mozilla.org/

這個擴充外掛是網友推薦,就看你要不要使用。
因為小弟有時候會調整網頁畫面,可以選擇性開啟。

Toggle Website Colors (Global)
https://addons.mozilla.org/en-US/firefox/addon/toggleglobalcolors/

它可以幫你把非白色的 背景 與 文字 改成 白底黑字 增加對比度。

調整前

示範網頁連結在此
很明顯有一個很大片的黑色底

調整後

找到 Toggle-Website-Colors 按鈕之後,無腦按下去

白底就來了。

Google chrome 瀏覽器 & Mircosoft Edge 瀏覽器

一個是 Google 出的瀏覽器,一個是微軟出的瀏覽器。
比較少人知道的是,它屬於同一個 Chromium 核心所以一起講。
而且後者 Edge 可以安裝前者的外掛,就可以一起講了。

Google Chrome 下載連結:https://www.google.com/intl/zh-TW/chrome/
Mircosoft Edge 下載連結:https://www.microsoft.com/zh-tw/edge

小弟這邊找了很久,有沒有類似 Toggle Website Colors (Global) 的擴充外掛呢?
小弟找到最接近的就是這個:

Change Colors
https://chrome.google.com/webstore/detail/change-colors/ageghplgcapnfpdhapeemolbmfccclke

但這個需要設定一下,他預設是黑底白字,需調整為白底黑字
在有需要的時候可以套用

這邊有二個地方需要手動調整

  • Background color: FFFFFF
  • Text color: 000000

設定背景跟文字的顏色,背景為白,文字為黑。其他的設定你可以視情況調整。

調整前

示範網頁連結在此

調整後

在你需要觀看的頁面底下,找到 Change color 的外掛按鈕,按下 Apply override on this page,然後 重新整理 網頁。

就會變成這樣

在電子紙螢幕看起來對比度更高。

如果要取消,就找到 Change color 的外掛按鈕,按下 Remove override on this page,然後 重新整理 網頁。

(他這個外掛似乎不會手動幫你重新整理,所以要重新整理網頁才會看到變更。)

以上就是小弟的設定值,希望你也可以跟我一樣調整出自己順手的設定。

參考資料

https://jingyi.blog/posts/mira_color_schemes/

[開箱分享] 文石 Boox Mira 13.3吋 電子紙顯示器 – 神級刷新的 E-ink 電子紙螢幕 上手體驗

boox mira 標題圖

不管工作或娛樂,電腦對於小弟我是生活不可或缺的工具,身為一個文字工作者(資訊工程師算是某種文字工作者吧?),平時工作寫程式、看網路文件、寫部落格,每天盯著盯著螢幕 10-12hr,眼睛擔負重責大任。除了日常的休息與保養之外,如果有什麼護眼成熟的產品,會想買來使用。電子紙是在之前初次接觸的時候就知道的產品,從後面幾代的 Kindle(小弟第一台的電子紙產品是 2020 年推出的 Amazon Kindle Keyboard 3gen),到近期的電子紙閱讀器,小弟一直有在關注電子紙的發展。


此系列文:


電子紙,初期見到他的感覺是 — 真的好像紙!,不自發光,本身只靠環境光源做閱讀,很優異的產品。(朋友第一次看甚至覺得很像假的 😂 )
但馬上就會發現它的缺點 — 刷新速度慢!相對於發展成熟的 LCD 液晶螢幕來說,刷新速度是用秒做為計算的,越來越發展直到看到這產品,這刷新速度優異到,快要媲美 LCD 的刷新速度了,當然是講「快要」,但至少到堪用的程度了,從當初官方測試影片釋出的時候我就心動下訂了,他們把電子紙推到另外一個新的境界了。
雖然還是黑白的,反應速度幾乎快跟的液晶無異了,就是這款文石 Boox Mira 13.3" 電子紙螢幕顯示器。
如今開放預購,接單生產,到真的拿到手上,也使用一陣子,到底是怎樣的體驗呢?
讓我們繼續看下去。

快速開箱

所有物品一覽

原廠似乎就一個大盒子,裡面有:

  • Mira 本體
  • Mini HDMI to HDMI 的線
  • Type-C to Type-C 的線
  • 說明書&保證書
  • 磁吸保護殼

原廠盒

Mira-開箱02

配件

設計很簡潔大方,利用黑色打出質感,磁吸保護殼顏色我也很喜歡,很有質感。

機器本體就是前鋁合金原色的感覺,自從入手蘋果 Macbook 之後,對於 Unibody 那種鋁合金金屬原色的產品都會愛不釋手 😅
有一點要注意的是,他的螢幕正面,不是全平面,是有做一個很窄的圓角框邊的設計,遠看不至於發現不是全平面。個人覺得,這樣的窄框邊設計,減少了一片電子紙螢幕本身透明面料的選擇減少透光率之外,如果不小心正面落下的話,不至於首先嗑碰到螢幕。

賣家附的有:

  • 不織布提袋
  • Mira 保護套
  • 小炫風遙控器
  • 螢幕支架座

平板支架01

平板支架02

平板支架03

非常可圈可點的是他附的支架座,有一般的夾具 與 加大的夾具,可以完整把 Mira 夾起來,角度調整也多,而且底座也很穩固。比較適合在室內使用,帶出去就稍微重了些。
保護套也很實用,保護力算蠻好的,不用另外去找了,很貼心。
小炫風遙控器的部分,目前這裏用不上,留到之後再評測。

硬體規格

規格

硬體規格(整理自文石官網):

  • 螢幕:13.3英吋柔性電子墨水螢幕
  • 解析度:1650×2200(207ppi)
  • 前光:冷暖雙色溫前光
  • 觸控類型:電容觸控
  • 介面:
    • Mini HDMI x 1
    • USB-C x 2
  • 螢幕掛架:VESA標準介面 (75x75mm)
  • 按鍵:2 個按鍵 + 1 個撥輪按鍵
  • 尺寸:308.4 x 231 x 5.8mm
  • 重量:約590g
  • 材質:全鋁合金機身(銀色)

Mira開箱-接口

這台 Boox Mira 電子紙螢幕,它有二個 USB Type-C 的插孔,對於都是 Type-C 的 Macbook 來說很友好,
一條 Type-C to Type-C 的線就可以直上了。

如果是別的機種,原廠有附 mini HDMI to HDMI 的線,也可以供電後直接插上使用。

快捷鍵就一個小撥動開關跟二顆按鈕,一顆按鈕為「重刷鍵」按下去會強制刷新整個螢幕。
一個搭配撥輪開關使用,可以調整 對比度、冷光前光、暖光前光。
(截自截稿,都還沒有辦法不用 app 調整來刷新模式)

他的保護殼屬於磁吸式的保護殼,實際使用上,我覺得應該要另外找架子來撐會比較理想。
雖然他有橡膠做止滑,但有時候他會滑掉。

如果有安裝螢幕架的需求,他也有提供標準的 VESA 來使用,
個人沒有安裝過,就無從評測。

實測使用

文章

經小弟電腦實測,
跟我的 2018 年的 Macbook pro 13" 很相容,跟官方實測影片完全一致,很理想。
但是跟 2020 年的 Macbook 13" M1 在灰色區域部分會有明顯閃爍現象(不知道是不是個案),已經反應給文石 Boox 請他們工程師查看
不過經由調整,加強對比度等,到一種還可以接受能使用的程度。

Mac 電腦的部分,需要安裝他的對應桌面版 app,藉由 app 來調整刷新模式

Mira開箱-設定圖

  • 一般模式 (個人推薦)
  • 影片模式 (防止抖動使用)
  • 文字模式 (黑白過濾,只剩黑與白)
  • 圖片模式 (採最高畫質輸出,手動刷新)

刷新速度、黑色加強、白色過濾、冷暖色光調整…等
都可以在 app 裡面調整

另外他可以觸控。個人沒有很常用就是
最近它 app 加入了自動刷新定時器,便利性又在更近一步了

您可以把佈景主題變成白色,電子紙在白色下比較有發揮。

它能不能看電影呢?

可以,播放是順暢的。看你大概不會想看。

實測-電影
(示範影片:末日列車 https://youtu.be/lGcJL6TG5cA

人物跟背景會黑成一團,看電影這種事情還是移到液晶螢幕上吧。

實測-動畫
(示範影片:一拳超人 https://youtu.be/2JAElThbKrI

實測動畫的部分,因為以線條為主的,清晰度來說還勉強能看。
(示範影片:Spring Boot Tutorial https://youtu.be/9SGDpanrc8U

實測-課程

實測線上課程的部分,基本上如果是投影片為主的話,都還算能看。
這邊也很明顯看到很多白色斑點,不是壞掉,而是演講者瘋狂移動滑鼠所造成的殘影。
如果是程式碼講解,基本上又回到黑底白字的情況的話,一樣很容易糊。

其他實測,
接上 Samsung galaxy S9 後會自動 DeX 模式,可以正常觸控,但螢幕比例跑掉,無法調整解析度
可以勉強使用,但整體體驗很差,就不多述了

疊疊樂

以下跟是 我的 Macbook pro 13"(左)跟 Boox Mira 13.3"(右) 做一個比較

疊疊樂-左右

Boox Mira(下)稍微比 Macbook pro(上)大了一些

疊疊樂 - 上方

厚度來說,不會差太多。

另一個角度。

缺點 (能改善之處)

目前硬體上的限制缺點:

  • 螢幕太小(只是存粹小弟想買 Mira pro 25.3 吋的版本 😂 )
  • 黑白(?) (算是對某些人來說是個缺點,但個人覺得還好)
  • 殘影

嗯對,殘影是會有的,只能透過定時刷新來解決這個問題。

殘影的圖片

個人覺得能改善之處:

  • 硬體鍵調整刷新模式,顏色過濾等選項,因為還是有可能有部分裝置不能連接 app
  • 可能需要動畫過濾的選項(或者避免螢幕閃爍之類的選項)
    - 加上輸出邊界縮放調整的選項,因為 Mac 接上螢幕時,邊角有部分一點點遮擋,雖不影響使用

總而言之,是個很棒的產品,
很適合文字工作者,需要文思泉湧的打字的時候,或者是像小弟我,需要時不時看很多網路參考文件,
其實我買電子紙閱讀器,使用最多的也是網頁瀏覽器,來源都是網路參考文件,不管是官方說明文件,或者是各種知識內容。
他會是你很理想的第二個螢幕。

[Q & A] 要不要貼保護貼?

多一層保護貼,會減少透光率,
如果要貼的話,可以貼霧面保護貼,減少反光。
我個人是沒有貼。

Mira 正面

Mira 反面

[Q & A] 外出攜帶保護怎麼樣

它原廠有附一個磁吸保護套,還蠻有質感的。
另外賣家有附一個專屬收納盒,這雙重保護個人覺得很夠用。
但還是老話一句,電子紙貴在它的螢幕,它不耐摔、不耐碰撞、不耐擠壓。
希望能好好呵護它。

另外,因為 13.3 吋的關係
(加上保護套比我的 13 吋 Macbook 再稍大一些),放進包會有一點稍大。

Mira開箱01

[Q & A] 關於線材,Type-C 接上螢幕沒反應?

直接講結論,請使用原廠附的那條黑色粗的那條線
小弟已經實測過,
蘋果的那條白色的 Type-C to Type-C 線,是確定無法辦法使用的。
(無法使用的情況就是,只會看到可愛的小老鼠睡覺的樣子,螢幕點不起來)

另外,如果要自己買線的話,可以挑有 E-mark 晶片的線材,可能可以解決問題
另外,它似乎 Type-A to Type-C 可以使用,但個人不確定供電是否足夠。
最好是外接電源使用。

我的設定值

Mira開箱-設定圖

分享一下我的設定值:

  • 顯示模式:普通 (適合網頁/文字閱讀)
  • 刷新速度:6
  • 深色增強 (對比度):10
  • 淺色過濾:50
  • 冷光:0 (不開)
  • 暖光:0 (不開)
  • 定時自動全刷:120 秒
  • 螢幕解析度:1280 x 960

其餘沒特別調整

參考資料

http://zh.boox.com/mira/
https://jingyi.blog/posts/mira_display_setup/
https://jingyi.blog/posts/mira_first_expression/

[iOS] 跟 Sign in with Apple 的愛恨情仇

最近終於研究出如何使用 Sign in with Apple 包含前後端的串接,分享給大家。
(本文同步於 iPlayground 2020 演講)


投影片:

iPlayground 影音版:https://www.youtube.com/watch?v=DwXhZwRDlzk


為什麼要串?

在 Apple 開發者大會 WWDC 2019 發表了 Sign in with Apple 的功能,iOS 13 後皆支援。

主要二個重點:

  • 使用 Apple ID 登入,強化保護您的隱私權
  • 當用戶不願給出自己的 E-mail 時,蘋果可以生成一組
    「虛擬 E-mail」給用戶使用,
    「虛擬 E-mail」收到的資訊,蘋果會代轉給用戶的「真實 E-mail」

官方消息指出,從 2019 年 9 月 12 日開始,新上架的 APP 需設置 Apple ID 登入;

在 2020 年 4 月之後,蘋果直接強制要求,
只要您的 app 有支援第三方登入 (例如:Facebook 登入、Google 登入、Twitter 登入…等)
就「一定要」支援 Sign in with Apple (硬要多一顆按鈕),相當於強迫中獎的概念。

基本上只要有帳號系統的服務網站,幾乎全部得加。

否則會怎麼樣呢? app 送審不會過,新 app 無法上架,新的 app 更新無法推上架提供更新。

串接 Sign in with Apple 勢在必行,因為 App Store 是 Apple 在管,官方擁有至高無上的權力。

Apple Sign In 的原理

Apple Sign In 主要接近於標準的 OAuth 流程,我們主要分成 網站 跟 App 二個部分來說:

App 登入

  1. 在 App 中,使用者按下 Sign in with Apple 按鈕,呼叫相關 AuthenticationServices framework 的相關函式,讓 iOS 系統跳出登入確認
  2. iOS 系統跳出 AppleId 登入確認,利用 TouchId 指紋辨識或 FaceId 臉部辨識(可選擇是否隱藏其 E-mail)
  3. 登入授權成功,系統呼叫 didCompleteWithAuthorization,我們處理該 Callback 將相關參數,用 API 傳回自己的網站
    (如果不做伺服器驗證的話,到這邊就結束了,但不建議)
  4. 在我們的網站 API 收到相關參數後,拿著參數向 Apple 驗證,並取得該使用者資訊
  5. 拿到使用者資訊之後,進行驗證登入,如果沒有帳號者會自動創立帳號

網站登入

  1. 在我們的網站,使用者按下 Sign in with Apple 按鈕,呼叫指定網址,其中帶入 Redirect URI 轉址回跳我們網站的網址,瀏覽 Apple 網站進行登入與授權
  2. 使用者在 Apple 網站進行登入,輸入 AppleId 的帳號密碼,
    並授權開發商授權允許(可選擇是否隱藏其 E-mail )
  3. 登入授權成功,轉跳回我們的網站,並附上一些的參數
  4. 我們的後端,拿著 Apple 傳回的參數向 Apple 驗證,並取得該使用者資訊
  5. 拿到使用者資訊之後,進行驗證登入,如果沒有帳號者會自動創立帳號

流程有一點接近但不太一樣,怎麼向 Apple 驗證這個之後會說。

虛擬 E-mail

剛剛有提到,當用戶不願給出自己的 E-mail 時,蘋果可以生成一組
虛擬 E-mail 給用戶使用,
這個虛擬 E-mail 是 亂碼@privaterelay.appleid.com
它是每個 App 專屬的,每一組 E-mail 都不一樣,
蘋果會驗證寄件人與寄件來源(不是什麼信件蘋果都會轉)

刪掉重裝又會產生一組新的虛擬 E-mail,讓使用者可以在他的設定頁,充分控制自己的個資露出於否。

但要實現這個功能,需要設定寄件人,讓指定的寄件人可以寄給使用者。

如果您是用自訂網域的話,則要綁定您的網域,蘋果他會用 SPF (Sender Policy Framework) 來驗證寄件來源。換句話說,如果要讓這個 虛擬 E-mail 能夠正常收到您寄的信,看你網域常用發信服務是誰,在您的網域 DNS 設定對應的 SPF DNS Record。

蘋果開發者網站設定

在 Apple 的開發者網站 ( https://developer.apple.com/ ) 當然要做很多設定

一樣分成網站 跟 App 二個部分來說:

App 部分

在 Certificates, Identifiers & Profiles -> Identifiers -> App IDs

按旁邊 + 號,選擇 App ID,選擇 App (不是 App Clip)
來創建您的 App ID(如果已經建立的話就修改它)

  • Description
    這裏填入您的 App 名字,
  • Bundle ID
    選擇 Explicit ,並填入 Xcode 裡的 Bundle identifier

(這應該是一般 IOS 開發者會做的事情)

今天要勾選 Sign In with Apple,並按下 Edit

  • Sign In with Apple: App ID Configuration
    留預設值 Enable as a primary App ID 即可
  • Server to Server Notification Endpoint
    這裡要填入一個網址,接收處理從 Apple 傳來的通知,後面會提到

網站部分

在 Certificates, Identifiers & Profiles -> Identifiers -> Service IDs

按旁邊 + 號,選擇 Service ID 來創建您的 Service ID
這裡有二個選項要填:

  • Description
    這裏填入您網站的名字
    (注意:這個值會顯示在前台網站給使用者看到)
  • Identifier
    你可以隨意取一個,作為辨識用
  • 勾選 Sign in with Apple 並按旁邊的 Configure

在 Web Authentication Configuration 頁面

  • Primary App ID
    選擇你主要 App 的 App ID
  • Register Website URLs

    • Domains and Subdomains
      這裏填入您網站的網域
      (注意:你的網站必須要有 https,不可用 localhost 或者 IP ,否則這裡不會過)

    • Return URLs
      這裡填入回跳用的 Redirect URI
      (一樣的規則,你的網站必須要有 https,不可用 localhost 或者 IP ,否則這裡不會過)

    如果是測試用的話,可以使用測試用的 Redirect URI:

    https://example-app.com/redirect

    (他是一個合法的網站,不是亂寫唬爛的)
    轉導到 example-app.com 之後藉由您手動複製貼上的方式把回傳值帶到你本機的測試程式中

Sign Key 驗證金鑰

我們需要建立一個 Sign Key 在等一下跟蘋果 API 做驗證使用,這部分因為網站跟 App 驗證流程後半段是一樣的,不管支援哪一個部分都要做。

在 Certificates, Identifiers & Profiles -> Keys

按旁邊 + 號,建立一個 Key

  • Key name
    可以自行取名
  • 勾選 Sign in with Apple 並按旁邊的 Configure
    • Primary App ID
      選擇主要 App 使用的 App ID
    • Grouped App IDs
      取名會把網站跟 App 群組綁在一起

按下 Continue 之後會讓你下載一個私鑰 p8 檔案,
注意這只能被下載一次,請好好保存。
如果不見的話就只能再重新產生一個。

設定寄件人、寄件網域

在 Certificates, Identifiers & Profiles -> More -> Sign in with Apple for Email Communication

按旁邊 + 號,輸入您的自訂網域與寄件人。

寄件人可以設定 Gmail 會直接通過。

如果自訂網域,需設定 SPF (DNS Sender Policy Freamwork)

設定 SPF

如果您是用自訂網域的話,則要綁定您的網域,蘋果他會用 SPF (Sender Policy Framework) 來驗證寄件來源。換句話說,如果要讓這個 虛擬 E-mail 能夠正常收到您寄的信,看你網域常用發信服務是誰,在您的網域 DNS 設定對應的 SPF DNS Record。

這遍設定各家有點不一樣

需要新增一個 TXT Record
值為

"v=spf1 include:_spf.google.com include:sendgrid.net include:amazonses.com ~all"

這個是範例值,裡面包含三個郵件服務 Email Service Provider (ESP)

  • Google (Gsuite) (Gmail)
  • SendGrid
  • Amazon SES

看您的郵件服務是哪一家,找到那家業者,照這個方式設定您允許的郵件服務存取您的網域

做一個簡單整理

  • CLIENT ID
    它可以是 App ID (也就是 Bundle ID) 也可以是 Service ID。

    • 如果要 App 端做登入,它就會是 App ID (也就是 Bundle ID)。
    • 如果要 網站端做登入,它就會是 Service ID。
  • REDIRECT URI
    OAuth 之後要轉跳的網址

    • App 的部分在 App ID 裡面做設定
      Certificates, Identifiers & Profiles -> Identifiers -> App IDs -> 您的 App ID -> Configure -> Return URLs
    • 網站的部分在 Service ID 裡面做設定
      Certificates, Identifiers & Profiles -> Identifiers -> Service IDs -> 您的 Service ID -> Configure -> Return URLs
  • TEAM ID
    你的開發者帳號 Team ID,這可以在你的右上角看到
    進去 Certificates, Identifiers & Profiles -> Identifiers -> App IDs -> 您的 App -> App ID Prefix 可以看見

  • KEY ID
    您建立驗證的 Sign Key 的 Key ID
    在 Certificates, Identifiers & Profiles -> Keys -> 您的 Apple Sign Key -> View Key Details -> Key ID 可以看到

  • SIGN KEY
    剛剛產生的 Sign Key 私鑰( p8 檔案 )

App 端實作

這裏你可以使用 Apple 所使用的 Sign In with Apple 按鈕,
Sign In with Apple 的按鈕 這裡

這裡有按鈕樣式規範與素材:
https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/

我使用的是直接呼叫的方式

import Foundation
import AuthenticationServices

class AppleSignInManager: NSObject {
    var currentView: UIView?

    func signIn(currentView: UIView) {
        guard #available(iOS 13.0, *) else { return }
        self.currentView = currentView
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.email, .fullName]
        request.nonce = "[NONCE]"
        request.state = "[STATE]"
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }
}

這邊我寫了一個 AppleSignInManager 來控制整個流程,做一個 signIn() 的 method 呼叫 Sign in with Apple。

要設定

  • state:一個您設定的,辨識用的字串
  • nonce:一個您產生的,辨識用的亂碼

來避免 CRSF 跨網域攻擊

extension AppleSignInManager: ASAuthorizationControllerDelegate {
    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, 
               didCompleteWithError error: Error) {
        // Handle error
        print(error)
    }

    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, 
               didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
        // Post identityToken & authorizationCode to your server
        print(String(decoding: credential.identityToken ?? Data.init(), as: UTF8.self))
        print(String(decoding: credential.authorizationCode ?? Data.init(), as: UTF8.self))
    }
}

這邊要實作一個 ASAuthorizationControllerDelegate,處理登入成功、登入失敗之後的動作,
登入成功要把 identityTokenauthorizationCode 等資訊 回傳給您的伺服器,繼續做驗證。

extension AppleSignInManager: ASAuthorizationControllerPresentationContextProviding {
    @available(iOS 13.0, *)
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return self.currentView?.window ?? UIApplication.shared.keyWindow!
    }
}

這邊要實作一個 ASAuthorizationControllerPresentationContextProviding,回傳目前當下 ViewController 的 window 給它,它會在 Sign in with Apple 呼叫後,把系統的畫面放在它上面。

網站前端實作

文件在這:
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple

你可以使用蘋果他預設給你的按鈕,

  • 字樣有 Sign in with Apple 或者 Continue with Apple 二種(包含多國語言)可以選
  • 樣式有 黑色白色 二種樣式,還有要邊框 (border) 與否可以選

或者你可以照個他的規範,自訂一個按鈕來用

按鈕樣式的規範與素材在這:
https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/

OAuth 跳轉模式有二種可以選:

  • 直接頁面轉跳
  • 開新視窗(彈窗)登入後關閉(一般 popup window 的做法)

前者較簡單,後者要自行處理流程

文件上提到的寫法就有二種,不過意思是一樣的:

<html>
    <head>
    </head>
    <body>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript">
            AppleID.auth.init({
                clientId : '[CLIENT_ID]',
                scope : '[SCOPES]',
                redirectURI : '[REDIRECT_URI]',
                state : '[STATE]',
                nonce : '[NONCE]',
                usePopup : true //or false defaults to false
            });
        </script>
    </body>
</html>
  • clientId:這遍填入你的 Services ID
  • scope:我都填固定值 name email (注意:中間有空格)
  • redirectURI:填入登入成功後要轉跳的網址,必須是 https 不能為 localhost 或者 IP
  • state:一個您設定的,辨識用的字串
  • nonce:一個您產生的,辨識用的亂碼
  • usePopup:是否用開新視窗(彈窗)登入,預設值是否

我使用的是自訂按鈕搭配 JavaScript 直接呼叫的方式:

async function doAppleSignIn() {
    try {
        const data = await AppleID.auth.signIn();
        console.log(data);
    } catch (error) {
        //handle error.
    }
}

上面的 AppleID.auth.signIn() 它會啟動整個 OAuth 流程。
如果是使用彈窗模式的話,會收到回傳的內容,如果是一般模式的話,會用頁面轉跳的。

成功登入時,如果是用轉跳的方式,會轉跳你設定的 REDIRECT_URI 並且傳入幾個參數供使用。

  • code
    單次使用的 Authentication code (效期只有 5 分鐘)

  • id_token
    identityToken 是一個包含用戶資訊的 JSON web token (JWT)

  • state
    你定義的字串

  • user
    一些 firstName, lastName, email 等使用者資料
    (不過蘋果這麼注重用戶隱私,也拿不到什麼資料)

如果是開新視窗登入,需要另外處理事件

//Listen for authorization success
document.addEventListener('AppleIDSignInOnSuccess', (data) => {
     //handle successful response
});
//Listen for authorization failures
document.addEventListener('AppleIDSignInOnFailure', (error) => {
     //handle error.
});

這裡你可以打你自己設計的 API,
identityTokenauthorizationCode 傳回給你自己的伺服器繼續做驗證

網站後端實作

JWT (JSON Web Token)

Apple Sign In 的資料交換主要使用 JWT (JSON Web Token) 的格式
(更明確點說,他是一個 JSON Web Signatures (JWS) 的格式)

在這之前,我們先釐清一下一些相關名詞:

  • RFC 7519 – JSON Web Token(JWT)
    定義了 header 內容與 claim 內容,以及 token 的相關規範

    • RFC 7515 – JSON Web Signature(JWS)
      定義如何做帶有簽章的 token
    • RFC 7516 – JSON Web Encryption(JWE)
      定義內容加密的 token
  • RFC 7517 – JSON Web Key(JWK)
    定義金鑰的格式
  • RFC 7518 – JSON Web Algorithms(JWA)
    定義加解密的演算法

所以

  • JWS 與 JWE 都是屬於 JWT 的一種。
  • 如果沒特別說明,則 JWT 皆是指 JWS。

我們再來說明一下什麼是 JWT

JWT 的全名是 JSON Web Token,是一種基於 JSON 的開放標準(RFC 7519),它定義了一種簡潔(compact)且自包含(self-contained)的方式,用於在雙方之間安全地將訊息作為 JSON 物件傳輸。而這個訊息是經過數位簽章(Digital Signature),因此可以被驗證及信任。可以使用 密碼(經過 HMAC 演算法) 或用一對 公鑰/私鑰(經過 RSA 或 ECDSA 演算法) 來對 JWT 進行簽章。

它是用 .(點)來分隔,主要有三個部分 base64UrlDecode 的內容:

  • Header
  • Payload
  • Signature/Encryption data

前二者用 Base64UrlDecode 解碼後,各會是一個 JSON 資料,
而 signature 故名思義就是一個用演算法算出來的 Hash

展開說明如下:

  • Header

    • alg
      必要欄位,對此 JWT 進行簽章、加解密的主要演算法 (JWA)。
      (這個名字叫做 JWA (JSON Web Algorithms) )
      這裡列出幾個常見的:

      • HS256 (HMAC-SHA256)
      • RS256 (RSA-SHA256)
      • ES256 (ECDSA-SHA256)

      第一項只有單向由同一把金鑰做雜湊 (Hash)。
      而後二者為非對稱式加解密,由私鑰進行簽名,由公鑰進行驗證。

    • typ
      JWT 本身的媒體類型,在 Sign In with Apple 這裡,
      我們使用預設值 JWT

    • kid
      這邊是 Apple 定義的 Sign Key 的 Key ID。

  • Payload
    • iss
      Issuer 的簡稱,表示發行者。
    • aud
      Audience 的簡稱,表示接收者。
    • iat
      Issued at (time) 的簡稱,即該 JWT 發行的時間,用 Unix timestamp 表示(POSIX 定義的自紀元以來的秒數)。
    • exp
      Expiration (time) 的簡稱,即該 JWT 過期的時間,格式一樣為 Unix timestamp。
    • sub
      Subject 的簡稱,用字串(case-sensitive) 或 URI 表示這個 JWT 所夾帶的唯一識別訊息。

怎麼驗證?

驗證方式有二種方式:

  • identityToken 用 JWT 的格式定義來驗證是否為 Apple 所簽發的
  • authorizationCode 用 OAuth 的機制向 Apple 伺服器交換並要求 Access Token

前後者不衝突,也可以二者都做,看你的需求。

驗證 IdentityToken

其實從 app 端拿到的 identityToken 它本身也是一個 JWT 格式

payload 用 base64UrlDecode 解開後可以得到類似以下的資料

Header 部分
{
  "kid": "86D88Kf",
  "alg": "RS256"
}

你可以看到他是用 RS256 (RSA-SHA256) 做加密簽章的,
由蘋果伺服器所擁有的私鑰進行簽名,由蘋果提供的 API 取得公鑰進行驗證。
它的 Key ID 為 86D88Kf (辨識是蘋果哪一把 Key 簽的,這等下會說)

Payload 部分
{
  "iss": "https://appleid.apple.com",
  "aud": "com.your.app.id",
  "exp": 1596621649,
  "iat": 1596621049,
  "sub": "001451.3dc436155xxxxxxxxxxxxxxxxxxxx59f.0447",
  "c_hash": "iUqI9Vyxxxxxxxxxg-CyoA",
  "email": "[email protected]",
  "email_verified": "true",
  "is_private_email": "true",
  "auth_time": 1596621049,
  "nonce_supported": true
}

取重點說明:

  • iss:發行者(issuer)
    https://appleid.apple.com,蘋果的伺服器
  • aud:接收者(audience)
    com.your.app.id (您的 App Id)
  • sub:Subject 主題的值,是使用者辨識唯一識別碼
    (可作為 使用者 ID 當做判斷依據)
  • email:使用者的 Email,範例值為虛擬 E-mail,
    如果用戶選擇不隱藏 E-mail,這裡就會顯示真實用戶的 E-mail
  • exp:過期時間
Signature 部份

從 Header 可以知道,
蘋果是用 86D88Kf 這把私鑰用 RS256 (RSA-SHA256) 簽的,等下取得該 86D88Kf 的公鑰就可以驗證它。

取得蘋果的公鑰

這裡蘋果有提供 API 來取得公鑰,

蘋果 API 文件在此:
https://developer.apple.com/documentation/sign_in_with_apple/fetch_apple_s_public_key_for_verifying_token_signature

用 GET 來打以下網址取得:

https://appleid.apple.com/auth/keys

它是一個標準 JSON Web Key Set (JWKS) 格式,
會得到類似這樣的資料

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "86D88Kf",
      "use": "sig",
      "alg": "RS256",
      "n": "iGaLqP..................zHLwQ",
      "e": "AQAB"
    }
  ]
}

礙於篇幅縮減了一下資料,重點說明:

  • kid:金鑰 ID,你可以找到剛剛範例的 86D88Kf 這個金鑰
  • alg:使用的演算法,範例值是 RS256
  • use:用途描述,白話文就是拿來幹嘛用的
    sig 意思就是拿來簽章用的
  • n:RSA 模數 (e),公鑰內容的一部分
  • e:RSA 指數 (n),公鑰內容的一部分

簡單來說,我們要:

  1. kid 找到對應的金鑰,
  2. ne 這二個值還原回 PEM 格式,
  3. 對 identityToken 做 Signature 驗證,檢核是否為蘋果伺服器發的
  4. 取用裡面的資料

以下使用 PHP 搭配 Lcobucci/JWTFirebase\JWT 套件來實作

require_once '../vendor/autoload.php';
function getUserDataFromIdentityToken($idToken)
{
    $token = (new Lcobucci\JWT\Parser())->parse((string)$idToken);

    $applePublicKeysRaw = curlGetAppleAuthKeys();
    $applePublicKeys = JWKParseKeySet($applePublicKeysRaw);

    $applePublicKey = $applePublicKeys[$token->getHeader('kid')];

    $signer = new Lcobucci\JWT\Signer\Rsa\Sha256();
    $keychain = new Lcobucci\JWT\Signer\Keychain();   
    if (!$token->verify($signer, $keychain->getPublicKey($applePublicKey))) {
        throw new RuntimeException("Key validation failed.");
    }

    if ('https://appleid.apple.com' !== $token->getClaim('iss')) {
        throw new RuntimeException("Source incorrect.");
    }
    $userData = array();
    $userData['email'] = $token->getClaim('email');
    $userData['id'] = $token->getClaim('sub');
    return $userData;
}

function curlGetAppleAuthKeys()
{
    $ch = curl_init('https://appleid.apple.com/auth/keys');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
    curl_close($ch);

    return json_decode($result, true);
}

function JWKParseKeySet($keySets)
{
    $parsed = \Firebase\JWT\JWK::parseKeySet($keySets);
    $pemKeySets = array();
    foreach ($parsed as $keyId => $sslKey) {
        $pemKeySets[$keyId] = openssl_pkey_get_details($sslKey)['key'];
    }
    return $pemKeySets;
}

function JWKVerify($idToken, $publicKeyPem)
{
    $signer = new Lcobucci\JWT\Signer\Rsa\Sha256();
    $keychain = new Lcobucci\JWT\Signer\Keychain();
    $token = (new Lcobucci\JWT\Parser())->parse((string)$idToken);
    return $token->verify($signer, $keychain->getPublicKey($publicKeyPem));
}

composer.json

{
  "require": {
    "firebase/php-jwt": "5.2.0",
    "lcobucci/jwt": "3.3.2"
  }
}

使用 AuthorizationCode 交換 AccessToken

這部分也就是最困難的部分

先貼蘋果 API 文件:
https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens

需要 POST 以下網址:

https://appleid.apple.com/auth/token

它定義了幾個參數

  • client_id
    如果是 App 登入就是 App ID (Bundle ID),
    如果是網站登入就是 Services ID。
  • client_secret
    一個 JWT 格式的要求文件,並利用前面申請的 Sign Key 來簽章
  • code
    填入 App 或 網站 收到的 Authorization Code
    (注意:Authorization Code 的效期非常短,文件上說最長只有 5 分鐘效期,
    但實際設定可能只有 30 秒至 1 分鐘左右)
  • grant_type
    這裡我們填 authorization_code 來交換 AccessToken

所以我們要:

  1. 自己組一個 JWT,並利用前面申請的 Sign Key 來簽章(指定使用 ES256 演算法),作為 client_secret,並帶入收到的 Authorization Code 作為參數,
  2. 打蘋果的 API

蘋果有給一個 JWT 的組成範例:

{
    "alg": "ES256",
    "kid": "ABC123DEFG"
}
  • alg:指定使用 ES256 (ECDSA-SHA256)
  • kid:你的 Sign Key 的 Key ID
{
    "iss": "DEF123GHIJ",
    "iat": 1437179036,
    "exp": 1493298100,
    "aud": "https://appleid.apple.com",
    "sub": "com.mytest.app"
}
  • iss:發行者(issuer)
    填入你的 開發者帳號的 TEAM ID
  • aud:接收者(audience)
    填入固定值 https://appleid.apple.com
  • iat:填入現在時間 (Unix timestamp)
  • exp:填入過期時間 (Unix timestamp),不能超過六個月
  • sub:Subject 主題的部分填入上述的 client_id
    如果是 App 登入就是 App ID (Bundle ID),
    如果是網站登入就是 Services ID。

最後簽章,組成一個 JWT 當作 client_secret 來打 API

成功的話會有類似以下的回傳值

{
  "access_token": "a6cab...........Y1A",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "r9d77fa9..........gfYA",
  "id_token": "eyJraW.................jMA"
}

其中一個重點, id_token 就是 identityToken,是我們要的資料,
解開後有:

  • sub :Subject 主題的值為 使用者 ID
  • email:值為使用者的 Email

這些前面有提到了,就不在重述。

Troubleshooting

雖然文件定義了很多錯誤訊息
https://developer.apple.com/documentation/sign_in_with_apple/errorresponse

大致會出現的只有幾種:

  • invalid_request 通常是連參數都弄錯
  • invalid_client 可能是 client_id 弄錯,
    client_secret (JWT 格式) 裡的 subclient_id 參數不吻合,或者根本沒這個 client_id
  • invalid_grant 這個是最常見(也是最難排查的)的錯誤,
    • 可能是 client_secret JWT 格式的加解密算錯 ⚠️
    • 可能是 Authorization Code 過期(沒即時打 API 做交換)
      (Authorization Code 效期超短,通常只有 1-2 分鐘,不超過 5 分鐘) ⚠️
    • 可能是 Authorization Code 已經被交換掉,變成無效 Code ⚠️
      (API 帶相同參數重複打就會出現)

Server to Server Notification

這個功能在新的 WWDC 2020 影片中推出,
截稿至今,蘋果還沒有寫 API 文件,只有釋出影片解釋 😓。

詳述在這裡

設定上去之後,蘋果會在用戶有帳號變更的時候通知你。

蘋果會用 POST 傳一個 json 格式給你,格式大致如下:

{
  "payload": "eyJraWQxxxxxxxxxxxxxxiUlMyNTYifQ.eyJpcxxxxxxxxxxxxxc0fSJ9.IUFWxPxxxxxxxxxxxxxxxxxbL3olA"
}

只有一個值叫做 payload,裡面也是一個 JWT 格式
你把 JWT 其中的 payload 用 base64UrlDecode 解開,會得到以下格式

{
  "iss": "https://appleid.apple.com/",
  "aud": "<Bundle Identifier>",
  "iat": 1508184845,
  "jti": "<unique events stream id>",
  "events": [
    {
      "type": "email-disabled",
      "sub": "<user_id>",
      "email": "<[email protected]>",
      "is_private_email": true,
      "event_time": 1508184845
    }
  ]
}

重點說明如下:

  • iss:發行者(issuer)
    https://appleid.apple.com,蘋果的伺服器
  • aud:接收者(audience)
    com.your.app.id (您的 App Id)
  • iat
    Issued at (time) 的簡稱,即該 JWT 發行的時間,用 Unix timestamp 表示。
  • jti:事件唯一碼
  • events:事件
    • type:目前狀態
    • sub:用戶唯一碼
    • email:用戶電子信箱(有可能是虛擬 Email)
    • is_private_email:是否為虛擬 Email
    • event_time:事件時間

其中 type 狀態有幾種:

  • email-enabled
  • email-disabled
  • consent-revoked
  • account-delete

祝串接順利。 🙂


參考資料

[iOS教學] 使用 PromiseKit 來管理你的 callback!

請參考 PromiseKit 的文件:

Promise 基本概念

在非同步執行的流程處理上,傳統作法一直是個麻煩點,
而 Promise 透過一些函式可以很直覺的管理非同步的流程。

傳統的做法

以 iOS 而言,你可能要透過 NSOperationQueue 或者
GCD (Grand Central Dispatch) 這些方法來做非同步的流程。

如果要接續觸發( A 事情做完做 B )的情境,A 事情的 callback 做完之後緊接著 B 事情,你可能會得到一個很深的縮排。
如果是互相等待完成( A 跟 B 事情 )的情境,你可能要透過一些 boolean 來把狀態記住,然後 A 事情跟 B 事情的 callback 同時要檢查對方是否做完,才接續另外一個 callback 。 以上並不是說這樣寫不能用,只是你會有更好的解法,讓程式碼變得更乾淨好閱讀。

Promise 的做法

主要關鍵字有 firstly then catch 還有 always
要字面上來看就是 「首先」、「然後」最後是有錯誤時用 catch 抓取錯誤。

這個範例還用了一個很實用的 when() 來綜合二件非同步的事情,當二件事情都結束時才會回傳到下一個 Callback。

這是一個綜合各種基本關鍵字的範例

firstly {
    // Show Loading status bar
    return when(myAsnycTaskA(), myAsnycTaskB())
}.then { (resultA:MyObjectA, resultB:MyObjectB) -> Void in
    // Show results
}.always {
    // Hide Loading status bar
}.catch { error in
    print(error)
}

最簡單的範例

這是一個最基本的範例
基本句型有 thencatch 就可以了

myAsnycTaskA().then { 
    (resultA:MyObjectA) -> Void in
    // Show results

}.catch { error in
    print(error)
}

宣告

至於你想要宣告一個事情也不難

func myAsnycTaskA()-> Promise<MyObjectA> {
    return Promise { fulfill, reject in
        // Done
        fulfill(result)

        // Fail
        reject(error)
    }
}

回傳一個 Promise,裡面有 fulfill()reject() 二個 method。
當資料回來的時候呼叫 fulfill()
當出現錯誤的時候呼叫 reject() 並帶入一個 Error 物件。

複雜一點的範例

這時回過頭來看第一個範例,我又把它改複雜了一點,是不是比較不難了呢?

firstly {
    // Show Loading status bar
    return when(myAsnycTaskA(), myAsnycTaskB())
}.then { (resultA:MyObjectA, resultB:MyObjectB) -> Promise<MyObjectC> in
    // So some processing
    return myProcessingTaskC()
}.then { (resultC:MyObjectC) -> Void in
    // Show results
}.always {
    // Hide Loading status bar
}.catch { error in
    print(error)
}

首先,同時做 myAsnycTaskA()myAsnycTaskB()
等二者結果回傳了之後,做 myProcessingTaskC() 最後回傳結果。

在 callback 的處理上是不是變得比較開心愉快了一點呢?
以上就是 Promise 的快速介紹。

有興趣的話,可以查看官方文件裡面有更進階的寫法。
這個概念不只在 iOS (Swift) 可以用,在 JavaScript (ES6) 也有類似的語法,有機會再專文介紹。

[教學] Mac OSX 內建防火牆 PF 使用筆記

<br />

你知道 Mac 本身也有內建防火牆嗎?
我指的不是 System Preferences 裡面有的防火牆(那個屬於 Application Firewall 指定程式進出的),而是可以像 linux iptables 可以設定規則的防火牆。

它叫做 Packet filter 簡稱 PF,因為 Mac OSX 屬於 BSD 系列的系統
自從 10.7 (Lion) 之後就有內建 PF(在這之前是 IPFW),只是預設是關閉的。

官方文件寫的非常少,大部分要參考 FreeBSD 的文件或者 OpenBSD 的文件,
大部分這二者看到的文件跟語法大多都支援。
介面只能用指令列,跟手動編輯文字檔的方式,沒有美美的 GUI 介面 😀

官方文件: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/pf.conf.5.html

指令是 pfctl 設定檔主要是 /etc/pf.conf

常用指令

這裡列出我常用的 pfctl 指令

檢查預覽規則

$ sudo pfctl -vnf /etc/pf.conf

如果設定檔有錯誤會在這裡顯示,反之顯示規則

重新啟動防火牆

$ sudo pfctl -d ; sudo pfctl -ef /etc/pf.conf

分號前半段是關閉防火牆,後半段是啟動防火牆,我把它合再一起比較方便

檢視狀態

$ pfctl -s state

pf.conf 設定

打開 /etc/pf.conf (需要 root 權限,可以用 sudo 拿到)
預設只有這樣

#
# Default PF configuration file.
#
# This file contains the main ruleset, which gets automatically loaded
# at startup.  PF will not be automatically enabled, however.  Instead,
# each component which utilizes PF is responsible for enabling and disabling
# PF via -E and -X as documented in pfctl(8).  That will ensure that PF
# is disabled only when the last enable reference is released.
#
# Care must be taken to ensure that the main ruleset does not get flushed,
# as the nested anchors rely on the anchor point defined here. In addition,
# to the anchors loaded by this file, some system services would dynamically
# insert anchors into the main ruleset. These anchors will be added only when
# the system service is used and would removed on termination of the service.
#
# See pf.conf(5) for syntax.
#
#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

Options (參數選項)

這個 Options 要在設定檔最開頭加入,不然會 Syntax error
不多說,直接看範例就明白

# 最佳化模式:一般模式
set optimization normal
# 阻擋的策略:直接丟棄
set block-policy drop
# 封包需要照順序
set require-order yes
# 跳過 lookback 介面不處理
set skip on lo
# log 紀錄只記最危急的等級
set debug urgent

其他細節可以參考這裡
https://www.openbsd.org/faq/pf/options.html

Anchors (規則群組)

Anchor 算是一個規則群組,你可以把你的規則分門別類。
例如這樣:

anchor "myrules"
load anchor "myrules" from "/etc/pf.anchors/myrules"

再來就是編輯你剛剛定義的 /etc/pf.anchors/myrules 檔案就可以了

當然這例子很粗淺,
你也可以使用連結裡的範例,用巢狀的方式寫出更複雜的規則
https://www.openbsd.org/faq/pf/anchors.html

Variables (變數)

變數定義就像是寫程式一樣定義字串,看範例就懂,也就不再贅述:

int_if="{ en0 en1 }"
ext_if="{ ppp0 }"
webports = "{http, https}"
int_tcp_services = "{domain, ssh, ntp, www, https}"
int_udp_services = "{domain, ntp}"
icmp_types = "echoreq"

跟 bash shell 一樣,井號 # 開頭的為註解,使用變數就是前面加錢字號 $

Tables (表格)

table <private> const { 192.168/16 }

這裡定義了表格,名叫 private,IP範圍在 192.168.x.x
這個 /16 是指 16bit 的子網路遮罩,等同於 255.255.0.0
用的是 CIDR 表示法
https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation

相關資料
https://www.openbsd.org/faq/pf/tables.html

Rules (規則)

這裡就是最複雜的地方了,建議文件先開起來看
https://www.openbsd.org/faq/pf/filter.html

文件裡有提到語法為

action [direction] [log] [quick] [on interface] [af] [proto protocol]
[from src_addr [port src_port]] [to dst_addr [port dst_port]]
[flags tcp_flags] [state]

有一個很大的重點是 規則是從上到下做比對,如果規則有衝突的話,會諄照 最後一條 符合的規則去執行。
要提一下 quick 這個關鍵字,如果有看到 quick 就代表只符合該規則,會無視後面寫(沒有 quick 關鍵字)的規則

例如文件寫的範例:

# 擋掉所有 ssh (TCP port: 22) 的連線
block in proto tcp to port ssh
# 允許所有連線
pass in all

規則第一行寫擋掉它,第二行又寫允許,假設有一個 SSH 連線, 該封包是被擋掉呢?還是被允許?
答案是被允許,因為規則如有衝突的話,諄照 最後一條 符合的規則去執行。

# 擋掉所有 ssh (TCP port: 22) 的連線
block in quick proto tcp to port ssh
# 允許所有連線
pass in all

第一行雖寫擋掉它,但因為有加 quick 所以會忽略之後所有符合的規則(忽略掉允許的規則)
所以假設有一個 SSH 連線,這次會變成 被擋掉。
這才是我們要的結果。

黑名單範例

黑名單顧名思義就是預設允許,只擋掉不要的
假設有台 個人開發機 要設定 PF 防火牆,需求如下:

  • 預設允許所有連出連入連線
  • 擋掉所有 80 (http), 443 (https) 的連入,只允許私有IP使用
  • 擋掉所有 3306 (mysql) 的連入,只允許本機連線
# === Developer machine example ===
# 指定連線介面
int_if="{ en0 en1 }"
# 指定要開放 連入 的服務
webports = "{http, https}"
# 定義私有IP範圍
table <private> const { 192.168/16 }
# 預設允許所有連出連入連線,並保持狀態
pass in all keep state
pass out all keep state
# 跳過 lookback 介面不處理
set skip on lo
# MySQL (TCP:3306) 允許本機 lookback 介面 能連線,阻擋其他的封包
pass in quick on lo proto tcp from any to any port 3306
block in quick proto tcp from any to any port 3306
# 只允許私有 IP 能連線,阻擋其他的封包
pass in quick proto tcp from <private> to any port webports
block in quick proto tcp from any to any port webports

測試

設定好了之後來測試,這裡列出幾個:

  • 是否本機能正常連線到 MySQL?
  • 找一台同網段的裝置,是否無法連接 MySQL?
  • 找一台接不同網路的裝置來測試,是否無法連接 MySQL?
  • 是否本機能正常看到自己開發中的網站?
  • 找一台同網段的裝置,是否能正常看到網站?
  • 找一台接不同網路的裝置來測試,是否能正常看到網站?

    附註:MySQL 連線指令為

    $ mysql -u root -h <YOUR_IP> -p

    測試 HTTP 伺服器

    $ docker run -d -p 80:00 j796160836/simple-test-http

    使用 docker 這個測試 image,
    對應內部 container 80 port (後者) 到主機 80 port (前者)

白名單範例

白名單顧名思義就是預設全部擋掉,再允許我要的連線,
這個設定比較複雜,要小心設定,要避免自己把自己鎖住出不去。

假設有一台 http 伺服器 要設定 PF 防火牆,需求如下:

  • 預設擋掉所有進出的連線
  • 只接受 80 (http), 443 (https) 的連入,其餘不打開
  • 基本對外上網要能用 (dns查詢、ntp網路對時、ssh、http)

這裡是簡化版,如有其他需要可以自行再加

# === HTTP server example ====
# 指定對外連線介面
ext_if="{ ppp0 }"
# 指定要開放 連入 的服務
webports = "{http, https}"
# 指定要開放的 連出 的服務
int_tcp_services = "{domain, ssh, ntp, www, https}"
int_udp_services = "{domain, ntp}"
# 跳過 lookback 介面不處理
set skip on lo
# 重新組合封包
scrub in all random-id fragment reassemble
# 擋掉所有 進入 的連線
block drop in log all
# 擋掉所有 連出 的連線
block out all
# 阻擋惡意偽造封包
antispoof quick for $ext_if
# 指定要開放 連入 的服務
pass in on $ext_if proto tcp from any to any port $webports
# 指定要開放的 連出 的服務
pass out quick on $ext_if proto tcp to any port $int_tcp_services
pass out quick on $ext_if proto udp to any port $int_udp_services
# 接受 PING 回應
pass on $ext_if inet proto icmp to $ext_if icmp-type "echoreq" keep state
# 接受使用 traceroute
pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state

測試

設定好了之後當然要測試,這裡列出幾個:

  • 該台伺服器是否能正確上網?是否能正常使用 ping, traceroute, nslookup 等功能?
  • 多個網路介面同時連線上網之後,對外介面 ppp0 斷線之後,是否無法上網? ping 會不會不通?
  • 找另一台接不同網路的裝置來測試,是否能夠 ping 到該台伺服器? http 是否能正確連入? 確認其他的 port 是否不能連入?

開機自啟動

PF 防火牆設定好了之後,可以這樣設定讓它開機自動啟動

$ sudo vi /System/Library/LaunchDaemons/com.apple.pfctl.plist

然後修改部分內容,以下列出全文

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Enabled</key>
<false/>
<key>Label</key>
<string>com.apple.pfctl</string>
<key>WorkingDirectory</key>
<string>/var/run</string>
<key>Program</key>
<string>/sbin/pfctl</string>
<key>ProgramArguments</key>
<array>
<string>pfctl</string>
<string>-e</string>
<string>-f</string>
<string>/etc/pf.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

其他規則範例

這裡我搜集了一些網友寫的規則,也蠻有參考價值的

https://gist.github.com/kujohn/7209628
https://gist.github.com/nathwill/9703175
https://gist.github.com/rosstimson/5826279
http://daemon-notes.com/articles/network/pf

注意事項

這裡講一些我遇到的雷,

  • pf.conf 跟 Anchors 文件的最後請多放二個換行,不然有時候會莫名遇到 Syntax error (這個最雷)
  • Mac OS X系統大版本更新之後(例如:El Capitan 升級到 Sierra),請倒回去確認防火牆規則是否還在,可能會被清掉
  • 設定防火牆時請避免使用遠端連線,避免重啟防火牆時被斷線或被錯誤的規則鎖在門外
  • 防火牆啟動後(尤其是採用白名單策略時),如果該電腦不能正常上網,切勿慌張,先查查看防火牆規則,是不是自己把自己給檔在門內出不去 😄

設定防火牆規則很複雜,請多測試,有時候不是你想的那樣,多參考別人的範例規則,再慢慢調整成自己想要的,good luck!

參考資料

http://www.thedeepsky.com/howto/newbiepfguide.php
http://www.scottro.net/pf.html
http://www.openbsd.org/faq/pf/options.html
http://www.openbsd.org/faq/pf/example1.html
http://www.openbsd.org/faq/pf/logging.html
https://www.freebsd.org/doc/handbook/firewalls-pf.html
https://forums.freebsd.org/threads/38669/
https://forums.freebsd.org/threads/11511/
https://forums.freebsd.org/threads/28101/
http://www.bsdforen.de/threads/pf-icmp-wahnsinn.14694/
https://forum.pfsense.org/index.php?topic=126031.0

使用 Markdown 來發文章吧!


這篇我看到文章的時候半信半疑,
想說真的可以這樣做嗎?於是乎就試試看,沒想到效果還不錯。

為什麼要學 Markdown?

Markdown 跟 BBCode, Wikitext 類似,但比檯面上的標示語言更為精簡好用,又是純文字文件,讀取不受各種編輯器的限制,好掌握。

簡單幾個要領 Markdown 快速上手

  • 井字號(#) 開頭的文句為 標題 (井號數量代表標題的大小)
  • 減號(-) 或 星號(*) 開頭的文句為 項目符號 (這個最常用)
  • 想要 1. 2. 3. 的項目符號?就直接打就可以了

    1. 這是第一項
    2. 這是第二項
  • 文句末 斷行前另外多加二個空白才是代表換行(這很重要)

這幾點學起來就很夠用了,想要進階一點可以看看這幾招

  • 開頭 二個星號 (**) ( 或底線 (_) ),中間包起來的字句為 粗體
  • (鍵盤左上方的)的頓號 ( ` ) 中間包起來的文句會做 特別標示
  • 連續三個頓號 ( “` ) (或者整個程式碼做 tab) 包起來的文句可當做程式碼
像這樣,這區域可以放程式碼
  • 小於符號 ( > ) 開頭的文句可以做為引用

像這樣,這裡可以用來引用文句

其他請見 Markdown 文件說明,亦可跟 HTML 混用

小弟部落格的需求

會寫一些技術文章,教學文筆記,開箱心得…等等,
會有不少上傳照片的需求(我比較喜歡每個步驟都有詳細的圖片)
部分會黏貼程式碼,程式碼希望能標明色彩或放在一個區域裡。

以前我的發佈文章流程

在 Windows Live Writer 寫好文章直接發佈至 PIXNET

以前用 Windows 電腦,使用 Windows Live Writer 的所見即所得 (WYSIWYG) 編輯器,照片也可以一併處理,還不賴。
缺點是只能單向發佈,發布後的文章無法回去 Windows Live Writer 做修改。
不過後來物換星移就沒使用了。

在 PIXNET 線上編輯器編輯,程式碼過水 Eclipse 再過水 Word

後來 PIXNET 的所見即所得 (WYSIWYG) 編輯器有做一些改進,自動儲存草稿功能還不錯,就索性直接在瀏覽器上邊輯了。
但是程式碼的部分不會自動標色,這部分 Eclipse 可以辦到,把程式碼複製貼上到 Microsoft Word 再複製貼上到編輯器上,這樣幾乎無損連同格式一起複製過來,方法繁瑣了一點,但部分空白鍵會被莫名的吃掉。
圖片的部分就直接在編輯器裡上傳整理。

現在有一個新的流程 — 使用 Markdown

用 MacDown 編寫 Markdown 文字內容,然後用 Export > HTML… 的方式,將 Markdown 文件轉成 HTML 語法(含預設的CSS)
然後在所見即所得 (WYSIWYG) 編輯器裡適時的加上圖片、設定文字顏色、超連結…等。

MacDown 軟體下載
https://macdown.uranusjr.com/

當然你可以不用 MacDown 這套軟體,可以隨你喜歡用別的,也可以用線上版本的,這部分可能要多方嘗試找到一套你順手喜歡的。

小缺點

缺點當然有

  1. Macdown 為單向輸出,已發佈的文章無法再變回 Markdown 再次編輯。
  2. 少了程式碼色彩標示,多了一些質感,少了一些方便。

對於日常發文等基本需求,我想應該夠用吧!

[2017.12.01] 更新

發現 MacDown 預設的 CSS 樣式會干擾部落格原有的 CSS 樣式,
導致部分破版問題,需要做一些 Patch。

需刪除這段 CSS 樣式

body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }

和這一段 CSS 樣式

@media screen and (min-width: 914px) {
body {
width: 854px;
margin:0 auto;
}
}

這樣就可以了 😀

參考資料

http://ebola777.pixnet.net/blog/post/57528840

[教學] 用 OpenWRT 做分離式上網設定 (以 小米路由器mini 為例)

這是一篇筆記備忘,因為設定很繁複,所以專門寫一篇做紀錄。
為何叫作分離式上網呢?其實這個名稱是我取的 😆

當然這個要用第三方韌體才做得到,官方韌體是沒這功能的 😛

以下所適用的情境是:

  • 切成多個 VLAN 區域,互相看不見彼此
  • 每個 VLAN 區域會分到不同的 IP 網段
  • 每個 VLAN 區域都有對應的 Wifi 無線網路供使用
  • 每一個 VLAN 區域要能夠對外上網

以下截圖採用 PandoraBox 的韌體在 小米路由器mini 上執行。
但因為 PandoraBox 是 OpenWRT 的版本分支,
LEDE 亦是 OpenWRT 的版本分支,
理論上 LEDE / OpenWRT 都能適用,只是版面排版不一樣。

什麼是 VLAN (Virtual LAN)?

引用 Wikipedia 的說明

A virtual LAN (VLAN) is any broadcast domain that
is partitioned and isolated in a computer network
at the data link layer (OSI layer 2).

翻譯一下, VLAN 是一個 OSI Layer 2 的獨立廣播區域。

虛擬區域(Virtual Local Area Network 或簡寫 VLAN)
是一種建構於區域網路交換技術(Switch)的網路管理的技術,
網管人員可以藉此透過控制交換機有效分派出入區域網的封包
到正確的出入埠,達到對不同實體區域網中的裝置進行邏輯分群
(Grouping)管理。

引用文章的說明

什麼是VLAN?VLAN是在同一物理區域網內
用於劃分若干個不同廣播域(子網)的技術,
子網內的主機可以互相通信,不同子網的主機之間不可互相通信。
什麼是VLAN ID?用於標識每個VLAN子網的ID。

範例環境

vlan.png

假設一台路由器有二個 VLAN

  • VLAN1
    • 路由器 IP: 192.168.1.1
    • 分配網段: 192.168.1.0/24
    • 無線網路 (2.4GHz) 名稱: PandoraBox
    • 無線網路 (5GHz) 名稱: PandoraBox_5G
  • VLAN2
    • 路由器 IP: 192.168.2.1
    • 分配網段: 192.168.2.0/24
    • 無線網路 (2.4GHz) 名稱: PandoraBox_lan2
    • 無線網路 (5GHz) 名稱: PandoraBox_5G_lan2

 

設定步驟

VLAN 設定

  1. Switch 頁面,設定好 vlan

Screen Shot 2017-11-20 at 10.57.58 PM.png

這邊的 Port 是指實體 RJ45 的有線接口,
可以通過插拔網路線的方法,對應到每一個 Port。
如果 Port 狀態為 untagged (不關聯),即該 Port 作為本 VLAN 成員,進行二層交換;
若選擇 tagged (關聯),Port 之間通信無二層交換,而是衝突廣播(類似 Hub 的方式)

簡單來說,

  • 要加入該 VLAN 的 Port 選 untagged
  • 不加入的 Port 選 off
  • CPU 照預設值,選 tagged
  • 其他的 Port 照預設值設定即可

以 小米路由器mini 來說,

4112272063a99d01 (1) copy.jpg

從左到右,分別是

Reset Button / USB 2.0 / Port 0 (White) / Port 1 (White) / Port 4 (Blue)

它多出一個特殊 Port 是 Port 7,其他機型沒有

  • Port 7 照預設值,選 tagged

以這個範例來說

原本

  • VLAN ID 1 (LAN): Port 0, Port 1
  • VLAN ID 2 (WAN): Port 4

改成

  • VLAN ID 1 (LAN1): Port 0
  • VLAN ID 2 (WAN): Port 4
  • VLAN ID 3 (LAN2): Port 1

因為有了 VLAN 這麼方便的功能,
你可以自由的定義每一個網路接口是什麼功用,不用照預設給的 WAN 是藍色, LAN 是白色的限制。

無線網路 Wifi 設定

Network > Wifi 頁面,在 [2.4GHz 頻段] 與 [5GHz 頻段] 各新增一個 SSID,
名稱為 PandoraBox_lan2PandoraBox_5G_lan2 並設定好 SSID名稱 & 密碼

Screen Shot 2017-11-20 at 10.55.20 PM.png

畫面上會有四個 Wifi,ra0 有二個,rai0 有二個
名稱個別為

  • PandoraBox
  • PandoraBox_5G
  • PandoraBox_lan2
  • PandoraBox_5G_lan2

Screen Shot 2017-11-20 at 10.56.15 PM.png

Screen Shot 2017-11-20 at 10.56.50 PM.png

Screen Shot 2017-11-20 at 10.56.28 PM.png

Screen Shot 2017-11-20 at 10.57.05 PM.png

邏輯介面 (Interfaces) 設定

Screen Shot 2017-11-20 at 10.50.10 PM.png

Network > Interfaces 頁面,新增一個介面,名字叫 lan2

Screen Shot 2017-11-21 at 2.01.35 AM.png

Screen Shot 2017-11-21 at 2.02.13 AM.png

Screen Shot 2017-11-20 at 10.51.50 PM.png

到 General Setup 設定 IP 資訊

  • IPv4 address: 192.168.2.1
  • IPv4 netmask: 255.255.255.0
  • IPv4 gateway: (留空)

到 Physical Settings 指定給定介面

  • VLAN Interface: “eth0.3” (lan2)
  • Wireless Network: “PandoraBox_lan2” (lan2)
  • Wireless Network: “PandoraBox_5G_lan2” (lan2)

到 Firewall Settings 設定

  • Assign firewall-zone LAN2: lan2

整理如下

第一個介面 – LAN

Screen Shot 2017-11-20 at 10.50.53 PM.png

Screen Shot 2017-11-20 at 10.51.13 PM.png

Screen Shot 2017-11-20 at 10.51.22 PM.png

第二個介面 LAN2

Screen Shot 2017-11-20 at 10.51.31 PM.png

Screen Shot 2017-11-20 at 10.51.45 PM.png

Screen Shot 2017-11-20 at 10.51.50 PM.png

介面設定 DHCP Server (二個 Interfaces 都要設定)

  • Disable DHCP for this interface 不打勾(或是按下 Setup DHCP Server
  • Router Advertisement-Service: server mode
  • DHCPv6-Service: server mode

Screen Shot 2017-11-20 at 10.54.57 PM.png

Screen Shot 2017-11-20 at 10.55.05 PM.png

防火牆區域設定

Screen Shot 2017-11-20 at 10.58.29 PM.png

Firewall > General settings 新增一個 Zone,
名稱叫 LAN2 ,並設定:

  • Input: accept
  • Output: accept
  • Forward: accept
  • Covered networks: lan2
  • Allow forward to destination zones: wan

整理如下

第一個防火牆區域 – lan

Screen Shot 2017-12-01 at 2.38.33 AM.png

第二個防火牆區域 – lan2

Screen Shot 2017-12-01 at 2.03.16 AM.png

Port forwarding 設定?

要做 Port forwarding (連接埠轉送 又稱 虛擬伺服器)?
當然可以,在 Firewall > Port Forwards 頁面
假設 對外的 80 port 要對應到 192.168.1.28080 port

就如此設定

Screen Shot 2017-12-01 at 2.06.49 AM.png

然後按下 Add,規則就訂好了

Screen Shot 2017-12-01 at 2.05.52 AM.png

可以按下 Edit 看詳細規則

Screen Shot 2017-12-01 at 2.06.08 AM.png

其他防火牆規則?

其實這都是把 linux 的 iptables 指令做成比較好用的 UI 而已,
在 Firewall > Traffic Rules 這個頁面,會有一些預設的規則,你可以參考這些現有預設的規則仿造一個你自己的

Screen Shot 2017-12-01 at 2.07.32 AM.png

再舉一個例子,假設要禁止 第一個介面(lan) 連到路由器的 telnet,但第二個介面(lan2) 不受限
可以如此設定

Screen Shot 2017-11-20 at 11.16.42 PM.png

如果你熟捻 iptables 指令,不妨直接去 FirewallCustom Rules 把規則直接打進去。

Screen Shot 2017-12-01 at 2.34.33 AM.png

 

參考資料

https://en.wikipedia.org/wiki/VirtualLAN
https://blog.chionlab.moe/2016/07/13/openwrt-multiwan-configuration/
https://www.dd-wrt.com/wiki/index.php/VLAN
DetachedNetworks(SeparateNetworksWith_Internet)

[教學] 小米路由器 mini 刷機紀實 (PandoraBox / Padavan / OpenWRT / LEDE)

xiaomiroutermi42.jpg

圖片來源:https://blog.jks.coffee/wp-content/uploads/2017/11/xiaomiroutermi42.jpg

其實 小米路由器 mini 這台已經出一陣子了,

官方的韌體也是不錯用但小弟有一些更進階的需求,
所以嘗試使用刷第三方韌體的想法,
網路上教學很多,多方參考才能降低失敗率
以下是我參考多方文件所得出的筆記

刷機有風險,刷機前請參考各方教學文 
步驟弄錯,變磚不要找我 😛

打開 SSH 介面

瀏覽官方 SSH 解鎖工具
前提要先把機器綁定到小米帳號底下,這網址才會有作用 (注意,該動作也會直接註銷喪失保固!

https://d.miwifi.com/rom/ssh

這裡會提供 root 密碼, SSH 工具 (miwifi_ssh.bin)

大略步驟如下:

  1. 刷入開發版 Rom
  2. USB隨身碟放入SSH工具 miwifi_ssh.bin
  3. 斷電,按住reset按鈕之後重新接上電源,長按約3-5秒 LED 變為黃燈閃爍,安裝後會自動重啟
  4. 連入 ssh 進行刷機

screencapture-d-miwifi-rom-ssh-1512040175668.png

打開 SSH 刷入 Breed 工具

Breed 是一款類似於 u-boot 的不死固件,類似手機的 Recovery
它放在 Bootloader 中,當 Firmware 有問題的時候,還可以經由進入 Breed 來做刷機

另外因為小米路由器的韌體架構的關係,韌體上面會有小米序號,
必須要先做備份,以免事後無法回復到原廠韌體。

  1. 查看 rom 資料
$ cat /proc/mtd
dev:    size   erasesize  name
mtd0: 01000000 00010000 "ALL"
mtd1: 00030000 00010000 "Bootloader"
mtd2: 00010000 00010000 "Config"
mtd3: 00010000 00010000 "Factory"
mtd4: 00c80000 00010000 "OS1"
mtd5: 00b11e68 00010000 "rootfs"
mtd6: 00200000 00010000 "OS2"
mtd7: 00100000 00010000 "overlay"
mtd8: 00010000 00010000 "crash"
mtd9: 00010000 00010000 "reserved"
mtd10: 00010000 00010000 "Bdata"
  1. 備份成檔案
cd /tmp
mkdir rom
dd if=/dev/mtd0 of=/tmp/rom/ALL.bin
dd if=/dev/mtd1 of=/tmp/rom/Bootloader.bin
dd if=/dev/mtd2 of=/tmp/rom/Config.bin
dd if=/dev/mtd3 of=/tmp/rom/Factory.bin
dd if=/dev/mtd4 of=/tmp/rom/OS1.bin
dd if=/dev/mtd5 of=/tmp/rom/rootfs.bin
dd if=/dev/mtd6 of=/tmp/rom/OS2.bin
dd if=/dev/mtd7 of=/tmp/rom/overlay.bin
dd if=/dev/mtd8 of=/tmp/rom/crash.bin
dd if=/dev/mtd9 of=/tmp/rom/reserved.bin
dd if=/dev/mtd10 of=/tmp/rom/Bdata.bin
  1. 複製備份到電腦上

用 scp 把備份檔案抓出來 $ scp -r [email protected]:/tmp/rom .

(註:scp 指令適用於 Mac / Linux 環境,
如果是 Windows 的使用者,請改用 pscp 或 WinSCP 來做資料傳輸)

恢覆指令 (以後備用)

mtd write /tmp/rom/Bootloader.bin Bootloader
mtd write /tmp/rom/Config.bin Config
mtd write /tmp/rom/Factory.bin Factory
mtd write /tmp/rom/OS1.bin OS1
mtd write /tmp/rom/rootfs.bin rootfs
mtd write /tmp/rom/OS2.bin OS2
mtd write /tmp/rom/overlay.bin overlay
mtd write /tmp/rom/crash.bin crash
mtd write /tmp/rom/reserved.bin reserved
mtd write /tmp/rom/Bdata.bin Bdata
  1. 刷入 Breed

用 scp 把 breed-mt7620-xiaomi-mini.bin 檔案複製進去

$ scp breed-mt7620-xiaomi-mini.bin [email protected]:/tmp
  1. 執行刷機
cd /tmp
mtd -r write breed-mt7620-xiaomi-mini.bin Bootloader

進入 Breed 介面

斷電,按住reset按鈕之後重新接上電源,長按3-5秒,LED 變為藍燈閃爍 插網路線,瀏覽 192.168.1.1 進入 Breed 介面

screencapture-192-168-1-1-1512040517285.png

選固件更新,在固件這欄刷入你要的 Rom

screencapture-192-168-1-1-upgrade-html-1512040580563.png

[PandoraBox]

檔名:PandoraBox-ralink-mt7620-xiaomi-mini-squashfs-sysupgrade-r1024-20150608.bin

下載位置

[Padavan (老毛子華碩第三方固件)]

檔名:RT-AC54U-GPIO-30-xiaomimini-128M_3.4.3.9-099.trx

下載位置

[OpenWRT]

檔名:openwrt-15.05.1-ramips-mt7620-xiaomi-miwifi-mini-squashfs-sysupgrade.bin

15.05.1下載位置

snapshots(開發版)下載位置

[LEDE]

檔名:lede-17.01.4-ramips-mt7620-miwifi-mini-squashfs-sysupgrade.bin

17.01.4下載位置

(下載連結均由網路搜尋而得,可能會失效,如有缺檔,請自行搜尋,小弟亦不補檔)

刷完手動重新整理,進入新韌體介面

這裡就留給大家動手玩玩各種韌體了

要注意,Breed 適用刷第三方 Rom,雖然也可以刷回小米官方韌體
但是 序號(SN) 會被洗掉不見,沒有序號就不能綁定小米帳號,也不能重新開啟 SSH 權限
如果要刷回原本官方韌體,要把原本備份下來的資料 (包含 Bootloader) 整個倒回去才行
如果成功的話,這時候應該會回到官方的開發版韌體

總結一下,小米路由器mini 這台其實沒有想像中的難刷機,刷了之後能用的功能就更多了,能調整的地方更細緻 第三方韌體穩定度蠻不錯的,適合中重度的玩家使用

參考資料

http://www.jianshu.com/p/111b6c1cbccf
http://eric0703.pentaxfans.net/2934
http://kyoko7384.blogspot.com/2017/03/mini-mod-iptv-rom-mod-padavan-rt-ac54u.html
https://leamtrop.com/2017/05/11/flash-openwrt-squashfs/
https://kirkhsutw.blogspot.tw/2017/06/mini.html
https://kknews.cc/tech/oyb2j3m.html
https://jingyan.baidu.com/article/c45ad29c24ee27051653e264.html
https://jingyan.baidu.com/article/3f16e003faa67e2591c10300.html
https://jingyan.baidu.com/article/215817f790b3ca1eda142333.html

[教學] Tomato 設定 OpenVPN 伺服器步驟 (以 RT-N16 為例)

有鑒於 OpenVPN 的 Server 設定官方文件又臭又長,常常望之卻步
小弟就自己的經驗做一個小小的分享,還有寫了一些方便自己的 Scripts
小弟使用的機器與資訊如下

硬體:Asus RT-N16
韌體:Advanced Tomato
Version 3.4-138    
K26USB-1.28.AT-RT-N5x-MIPSR2-3.4-138-AIO.trx
(OpenVPN version: v2.3.11)
設定使用的電腦:Mac

設定步驟

1. 下載檔案

首先先下載 easy-rsa

https://github.com/OpenVPN/easy-rsa/releases/tag/2.2.2

和小弟寫的 Scripts

https://github.com/j796160836/tomato-openvpn-setup

因為這版的 Tomato 使用的是 OpenVPN v2.3.11
所以搭配 easy-rsa  v2 做設定

然後解壓縮跟我的 Script 放在一起

2. 修改 vars 變數檔案

$ vi vars

把設些改成你要的內容

export KEY_SIZE=2048
export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="SanFrancisco"
export KEY_ORG="Fort-Funston"
export KEY_EMAIL="[email protected]"
export KEY_OU="MyOrganizationalUnit"
export KEY_NAME="EasyRSA"
export KEY_CN="CommonName"

3. 產生伺服器金鑰

generate-keys-server.sh  指令

$ ./generate-keys-server.sh

他會把剛剛的環境變數設定好,並清掉之前有產生的金鑰

產生一個預設名字的伺服器金鑰,最後印出來

如果你之前有跑過這個指令,就不要再跑第二次了

(如果要跑第二次,就要重新設定用戶端金鑰)

執行成功會出現類似這樣的東西

================================================================
Please paste it at Tomato's OpenVPN key config page
================================================================
[Certificate Authority]
-----BEGIN CERTIFICATE-----
MIIDhDCCAu2gAwIBAgIJAJ9a8dYhq73TMA0GCSqGSIb3DQEBCwUAMIGJMQswCQYD
Q6Xp2vd6068LHC7b9qTs1nWHfYbpdOv1GmzKONIGd3FDiuRFJu4J/g==
-----END CERTIFICATE-----
[Server Certificate]
-----BEGIN CERTIFICATE-----
MIID5jCCA0+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMCVFcx
TRmjwzCO84lz2LOYFDVlETjb6Mb76SysoHVb4zNPX7Bkrr2u3c8+vzaV
-----END CERTIFICATE-----
[Server Key]
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCy1Pwvi8bE0dTpNddzNBluQoKHdHQpa7mBuH7T0+fWLhs3HoEd
POvdTEVwh+G/2kce8xtOwJAkvyppXMWjY2WmSqimVP8=
-----END RSA PRIVATE KEY-----
[Diffie Hellman parameters]
-----BEGIN DH PARAMETERS-----
MIGHAoGBAN8PIYvlZy1rDghoF+K9wmMrCaN5DBi+3HPFemJEZK4wlyeXHLDOGYx+
5vtwBR2tPYXwTwdeMZItmqVMsVuIN4d0vEzDrbNihAU7OaaWzP+bAgEC
-----END DH PARAMETERS-----

(請你自己跑一次,不要直接照樣 copy,這裏的金鑰只是示意而已)

你就打開 Tomato 的 VPN > OpenVPN Sever > Keys 進入金鑰設定頁面

把金鑰對應地複製貼上到對的地方

1

如果它跑太快或者你不小心把結果清掉了,你可以再跑一次 print-server.sh 指令查看

$ ./print-server.sh

4. 抓取 Tomato 上的設定值

這裡有點 Tricky,我是直接讀取它的設定檔指令來做的

請使用 Tools > System Commands (System Shell) 來執行 fetch-server-config.txt 檔案裡面的指令

2

這個是 fetch-server-config.txt 檔案裡面的內容

#!/bin/bash
server_config="/etc/openvpn/server1/config.ovpn"
if [ ! -e $server_config ]; then
echo "File not found!: $server_config"
exit 1
fi
port=cat $server_config | grep "port" | awk '{print $2}'
public_ip=curl -s ipinfo.io/ip
#public_ip=curl -s checkip.dyndns.org | sed -e 's/.*Current IP Address: //' -e 's/<.*$//'
config_data="remote "$public_ip" "$port"n"$(cat $server_config | grep "proto|dev|cipher" | awk '{printf "%s\n", $0}')
echo "=================================================="
echo "Please paste this command in your easy-rsa folder"
echo "=================================================="
echo
echo "echo -e ""$config_data"" > server-config" 

執行之後會得到類似這樣的指令

3

==================================================
Please paste this command in your easy-rsa folder
==================================================
echo -e "remote 1.2.3.4 1194nproto udpndev tun21ncipher DES-CBCn" > server-config 

一樣,把剛剛產生出來的指令,貼回 Mac 的 Terminal 上,產生 server-config 這個檔案

5. 產生用戶端金鑰

這裡執行 generate-keys-client.sh 這個指令,並打入你要的名稱

$ ./generate-keys-client.sh Client01

這裡我打的是 Client01 做為名稱,你可以換成你要的,但不能重複

執行成功會得到類似這個訊息

Write out database with 1 new entries
Data Base Updated

就完成產生了

6. 產生用戶端OpenVPN設定檔

使用 print-client-config.sh  這個指令,帶入剛剛打的名稱,並寫入進一個 ovpn 檔案之中

$ ./print-client-config.sh Client01 > Client01.ovpn

他就會抓取金鑰跟剛剛 server-config  裡面的內容產生一個設定檔

接下來你只要把 OpenVPN 的用戶端安裝好,使用剛剛的 ovpn 設定檔就可以了

以 Mac 為例,

Mac 的 OpenVPN 用戶端使用 Tunnelblick

https://tunnelblick.net/

安裝完成,雙點 ovpn 檔案,再輸入電腦使用者的密碼就可以使用了

IOS 的話有 OpenVPN Connect 可以使用

https://itunes.apple.com/us/app/openvpn-connect/id590379981?mt=8

把 ovpn 檔案放在 iCloud drive 然後載入它,就可以了

 

祝設定成功!