[教學] 透過 FRP 讓內網裡面的 Home Assistant 也能用 https 連入

前言


自己本身是中華電信的用戶,家中的網路本身就可以取得一個固定 IP 以及七個浮動 IP(全都是 public IP),因此透過設定 router 的 port forwarding 就可以很簡單的讓內網的服務能夠讓外部存取。

不過在一些討論區中看到很多人,由於家中使用社區網路或是 LTE 等不給予 public IP 的網路服務,導致要達到外網存取內網的服務困難重重。以 Home Assistant 來說,最簡單的解決方法,就是使用 Nabu Casa 的雲端服務,一個月五塊錢美金,提供免開 port 的內網穿透服務以及簡易的 Google Home 等的整合,又可以贊助 Home Assistant 的開發團隊(Nabu Casa 是 Home Assistant 開發團隊所開的公司)。

雖然五塊錢美金我覺得很便宜,本身也有贊助兩個帳戶,但要收費這點還是讓許多人怯步。

之前就有看到一些達成內網穿透的方式,但因為自己沒有這方面需求,也就一直沒有研究的動力。最近看到許多人都在詢問無外部 IP 的解法,剛好最近也在玩一些雲端的 VPS(Virtual Private Server),前幾週就花了一個晚上研究了一下這方面的方案,這邊就分享一下整理的心得。

先說這是一個「不完整」教學,如果你想找的是能跟著一步一步走完成所有步驟的那種教學,不好意思這並不是。因為雲端服務的部分提供者太多,Google 啦、Amazon 啦、DigitalOcean 啦什麼的一堆,各家雖然建置的流程很類似,但需要的基礎知識實在有點多,我個人是蠻討厭只講步驟卻不解釋為何要那樣做的教學, 但這塊領域範圍又太大,我實在沒有自信也沒有那麼多篇幅可以把各面向講解得很清楚,因此雲端主機的部分我只提必要重點,細節部分就要請需要的人自行去嘗試跟在別處挖資料補齊了。

本篇主要的重點將會放在如何使用 FRP(Fast Reverse Proxy)以及 Home Assistant 內建的各項 add-on 達成沒有外部 IP 的情況下也能存取內網內 Home Assistant 的目的。


預計達成效果


可透過 https//mydomain.duckdns.org 連結內網內的 Home Assistant,即使是社區網路或 LTE/5G 等不提供 public IP 的網路服務皆可,可解決使用上述網路服務無法使用 port forwarding 以及一般 DDNS 服務連線的問題。


完成本篇教學需要的準備


本篇教學有一定程度基礎知識的要求,請斟酌參考。

  1. 一個能提供外部 IP 的雲端 VPS 服務平台(我自己測試了 DigitalOcean 以及 Oracle Cloud)。
  2. 知道怎麼用雲端服務平台建立一台 Linux 虛擬主機。
  3. 有 Linux CLI 指令的基礎概念(透過 SSH 以及文字指令介面完成各項操作的能力)。
  4. 完整的 Home Assistant 環境,教學中會用到 File editor、DuckDNS、NGINX Home Assistant SSL proxy 等預設 add-on,如使用其他同類型服務請自行轉化應用。

FRP 以及其運作概念


FRP 的作者有中文說明頁,建議有興趣的人可以先閱讀一下。簡單來說,FRP 讓你透過一台有外部 IP 的主機作為外部連線的中繼點,讓另一台內網裡的電腦能將服務藉由它穿透出去,是一個反向代理的應用。

由於一些社區網路服務以及 LTE 的服務僅提供內部 IP,所有連線的電腦猶如在一個大內網內,因此無法像中華電信那樣直接有外部 IP 可用,只需要於 router 上打開服務用到的 port(例如 HA 用的 8123)就可以由外部連入,因此使用這類型網路服務的朋友就會面臨一到外面就無法連線家中 HA 的問題。

FRP 本身分成兩個部分,一個是 frps(FRP Server)放置於有外部 IP 的主機端,一個是 frpc(FRP Client),通常用於內網中需要將服務暴露到外網的電腦上。

在內網的 frpc 連線到 frps 後建立通訊的管道,而 frps 那台電腦就能將外部連線的請求轉發到指定的內網電腦上,達到內網穿透的目的。從簡略的運作原理上我們可以了解要能讓這個應用成立,有一台有對外 IP 的電腦是很重要的。

可是我們不就是因為沒有外部 IP 可用才要找解決方式嗎?這台有外部 IP 的電腦要怎麼生出來呢?

這時我們就要應用現在盛行的雲端服務平台,在這類型平台上建立一台有對外 IP 的虛擬主機,並在上面放置 frps 作為外部的連線窗口。整個運作邏輯就是這樣,當然如果你在別的地方有電腦 24 小時開著又有對外 IP,也是可以拿來放 frps,簡單來說就是一台你有控制權,可接受外部連線的電腦即可。

FRP 的設定方式,是透過對應的 frps.ini 以及 frpc.ini 兩個檔案去定義,只需將這兩個檔案設定好並讓 frps/frpc 去讀取,就會依照設定內的參數去運作。


frps 的設定要點


大部分的雲端服務會依照你所建立的虛擬主機使用多少資源來計算應支付的費用,像我自己最近在玩的 DigitalOcean 就是如此,使用越多 CPU 核心、越多 RAM、越多磁碟空間、越多頻寬…,每個月結算的金額就越高。所以之前我就覺得與其這樣不如就用 Nabu Casa 服務就好了,因為依照我自己在 Google、Amazon、DigitalOcean 建立一些服務的經驗,很容易就會超過五美金,因此自行用雲端建置似乎沒有那個必要性,除非說想要使用自己的域名(Nabu Casa 提供的是一個非常長串的無意義子域名),不然並沒有解決「要花錢」的問題。有些雲端服務商為了吸引開發者去嘗試服務,初期會提供一些免費額度讓人試用,但最終還是免不了要付費。

直到之前在討論群里看到有人分享使用 Oracle Cloud 免費服務來當作 host 的影片,剛好可以解決費用問題。有興趣的人可以依照影片內的指引去申請一個 Oracle Cloud 的服務來用,由於已經有相關的影片,我就只針對重點講解。

首先在主機上下載並解壓縮 FRP 的套件,並編輯 frps.ini 的內容,內容如下:


[common]
bind_port = 7000  #frpc 與 frps 建立通訊管道要用的 port
token = some_token. #自行定義一個字串作為通訊認證用,frps/frpc 相同即可

基本上這樣的參數就行了,有需要其他功能的請參閱官方文件

接下來打入指令啟動即可等待 frpc 連線進來:


./frps -c ./frps.ini

如果你建立的虛擬主機或使用的電腦有設定防火牆,記得允許 bind_port 參數設定的 port 連入(以上面例子來說是 port 7000)。接下來另一個問題是,frps 並不是以一個系統服務的形式啟動,因此一但程式被關閉或是重啟主機,你就得手動再啟動才能恢復運作,這樣似乎有點麻煩,因此我們可以將 frps 設為 service 讓它可以自動啟動。

以下是 /lib/systemd/system/frps.service 的內容(範例,細節可以搜尋如何在 Linux 底下加入自訂的 service):


[Unit]
Description=FRP Server #service 的敘述
After=network.target

[Service]
Type=simple
Restart=on-failure
RestartSec=5s
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini #frps 以及 frps.ini 的路徑

[Install]
WantedBy=multi-user.target

建立好之後 reload 一下:

systemctl daemon-reload


設定開機時自動啟用服務:

systemctl enable frps


如果需要手動啟動或停止服務:

systemctl start frps
systemctl stop frps


如果需要查詢服務狀態:

systemctl status frps


初期測試的時候我是直接執行,等到測試運作無誤後,再寫成 service,因為 frps 執行時如果 frpc 有連線進來或有什麼錯誤都會有顯示,查問題會比較方便。基本上對外主機端的重點就是:

  1. 注意連線用的 port 不要被防火牆或網路介面設定擋住
  2. Token frps 與 frpc 要相同
  3. 將 frps 寫成 service 自動於開機時執行

整體來說其實設定蠻單純的。


於 Home Assistant 上安裝 frpc 客製套件


在這篇教學,我以官方版本的 Home Assistant 作為範例,由於 Home Assistant OS 預設並不是那麼容易存取,還好有人熱心寫了客製套件可以用。如果是使用 Home Assistant Supervised 的方式安裝,可以直接對 OS 動手,就直接執行 frpc 或是同 frps 寫成 service 即可。

首先先來把這個 frpc 的客製套件加到 add-on 中:

到 Add-on Store 中,按下右上角三個小點圖式,選 Repositories。


這個專案的連結複製並加入到文字框中,然後按 ADD 加入。


連結沒有貼錯的話應該會正常顯示加入的狀態。


然後 Add-on Store 中應該會出現 FRP Client 套件的選項,點進去後安裝。


安裝完後別急著啟動,先用 Text editor 到根目錄底下找到 /share/ 資料夾,在底下建立一個 frp 資料夾,
在底下再建立一個 frpc.ini 檔案(完整路徑為 /share/frp/frpc.ini),檔案內容如下:


[common]
server_addr = 123.123.123.123 #這裡輸入 frps 所在主機的 IP 位址
server_port = 7000 #與剛才在 frps.ini 裡的 bind_port 相同的 port number

# console or real logFile path like ./frpc.log
log_file = /share/frp/frpc.log

# for authentication
token = some_token #與剛才在 frps.ini 裡相同的 token

[https] #方便辨識不同服務的名稱
local_ip = 10.0.10.100 #Home Assistant 所在機器的 IP 位址
local_port = 443 #待會要用 NGINX Reverse Proxy 所以用 443(https)
remote_port = 443 #frps 主機端想要直接打 https 不打 port number 所以也用 443


*記得這邊 remote_port(有對外 IP 的主機端)要用的 port,在主機端也要打開,別忘了確認防火牆設定。

**Text editor 無法看到 /config 更上層的路徑的話,記得到設定中把 enforce_basepath 設為 false(預設是 true)。

設定好後,啟動 FRP Client add-on,可以去 log 中看一下是否有正常連到 frps。都正常的話,接下來我們要透過 DuckDNS 取得一組免費的域名順便申請安全憑證。

有需要啟用其他服務的話,就依照 [https] 那個區塊的定義方式再加上去即可,例如我想要加上 port 80(http),就加上下面定義:


[http] 
local_ip = 10.0.10.100
local_port = 80
remote_port = 80


設定 DuckDNS 並完成憑證申請


DuckDNS 的申請流程以及基本設定請參閱這一篇教學,我們這邊主要要增加一條設定。以往我們用 DuckDNS 大都是為了要搭配動態 IP 而使用,但由於使用雲端虛擬主機 IP 是固定的,如果 DuckDNS 自己抓到 client 端的 IP 又自動更新 DNS 紀錄,會造成用網址連線時會是錯誤的位址,但不開 DuckDNS 的話,安全憑證又不會自己更新…

還好 DuckDNS 有參數可以指定要回報的 IP,如下面範例:

lets_encrypt:
  accept_terms: true #接受 Let's Encrypt 的使用條款並申請安全憑證
  certfile: fullchain.pem
  keyfile: privkey.pem
  algo: secp384r1
token: some_token #DuckDNS 網站上申請帳號取得的 token
domains:
  - mydomain.duckdns.org #DuckDNS 子域名
aliases: []
seconds: 300
ipv4: 123.123.123.123 #這邊設為 frps 那台主機的 IP

只要加上末端那個 ipv4: 參數,DuckDNS 就不會去抓 client 端的 IP,而會永遠回報此固定 IP 位址給 DuckDNS 主機。到此,DuckDNS 就設定完了,現在當我到瀏覽器輸入 mydomain.duckdns.org,這個網址自動會對應到 frps 端主機的 IP。接下來只要再設定 NGINX reverse proxy 就完成了。


設定 NGINX Home Assistant SSL Proxy


一樣,到 Add-on Store 中找到 NGINX Home Assistant SSL Proxy 並安裝,安裝好後到設定裡面去把 DuckDNS 申請到的子域名輸入即可。


domain: mydomain.duckdns.org #把 DuckDNS 子域名加入即可
certfile: fullchain.pem
keyfile: privkey.pem
hsts: max-age=31536000; includeSubDomains
cloudflare: false
customize:
  active: false
  default: nginx_proxy_default*.conf
  servers: nginx_proxy/*.conf


由於我們有使用 reverse proxy,因此要在 `configuration.yaml` 裡加上以下的設定,否則連線時會顯示錯誤:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.30.33.0/24


現在到瀏覽器上,輸入 mydomain.duckdns.org,應該就可以連到自己的 Home Assistant web UI 了,而且 client 端完全不用在 router 設定 port forwarding 之類的(設定了也沒用,因為是在大內網裡面)。


以上整個流程我是用官方 Raspberry Pi 4B 磁碟映像檔做的乾淨環境測試的,網路是使用華為的 LTE router 搭配遠傳的 SIM 卡。最早開始測試時是使用中華電信 SIM 卡用 iPhone 分享出來的熱點,Home Assistant 則是跑在 VMware Fusion 上,一樣是官方的 VM 檔案。

所以只要 frps 端有弄好,整個流程依照上面的方式應該是可以成功的,有問題就在下方討論,以上。

5個讚

超佛心的分享~~

1個讚

Edwin大的分享,必屬佳作
雖然我自己的caddy或caddy2都沒問題,這篇收藏起來以防萬一
感謝教學

1個讚

承蒙這篇的指導,我終於可以順利以HTTPS接上家用的樹莓派HA
由於我的環境有兩個區域(我家和隔壁家)都用樹莓派來架設,並且都用同一台公網主機當FRPS
想說也特地寫來分享
寫在前頭,記得在區網試,如果改錯了至少還能進區網改,遠端改錯了就連不上

  1. FRPS端按照上面填寫以外,新增加一行
    vhost_https_port = 443

  2. FRPC端
    原本的HTTPS區段可以先# 起來
    複製改成如下
    [https]
    type = https
    local_ip = 192.168.xxx.xxx(這個就自己填上自家HA區網的IP)
    local_port = 443
    custom_domains = mydomain1.duckdns.org(這個就是去DUCKDNS新增的DOMAINS)

  3. 假如有好幾個家的HA,那麼就在各自的FRPC設定檔frpc.ini撰寫
    custom_domains與[https]字樣要記得換,不然FRPS的LOG會出現名稱已經用過的錯誤,或是無法連上

重開後各個HA都能用各自的DOMAIN連上

2個讚

博主你好:
感谢你的教程!我照做基本配置好了相关的addons,但是遇到一个常见的问题,还好最终解决了。估计其他朋友也有可能会遇上,特来分享。
我的VPS不允许bond 443端口,所以我只能用其他端口替代。

frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
vhost_https_port = 8081
token = my_token

frpc.ini
[common]
server_addr = my_VP_ IP
server_port = 7000

log_file = /share/frp/frpc.log

token = my_token

[https]
type = tcp
local_ip = 127.0.0.1
local_port = 443
remote_port = 6300

然后我就可以https访问HA了,我不太介意加上端口
https://mydomain.duckdns.org:6300

感谢你的教程!

1個讚

請問E大,因為SSL加密照著做依然出問題,想說直接買服務比較快,但是使用Nabu Casa時,下方有免費試用一個月,點入後創建帳號送出好幾次都一直顯示"Unexpected error",請問這要如何處理?

昨天照著E大的方法做,終於成功透過外網連回來
不過發現一個問題:ha登入記錄client ip一直顯示192.168.1.xx (ha主機),無法顯示外面連進來的真實ip,這樣好像沒辦法ban異常登入的ip,不曉得E大有沒有解決辦法?