2025/10/08

Laravel Sail On WSL

緣由

我開發 PHP,不管是純 PHP 或 Laravel 都很喜歡用 Homestead,他優點是可以起一台 VM 就可以同時使用多個專案,而且透過修改一個簡單的 Homestead.yml 就可以做完全部環境的設定,但官方已經宣布不維護了,身為資訊人,你要嘛 fork 一份自己維護,不然就要跟著時代走。

不維護的原因也很簡單,VM 真的很肥很吃資源,現在 docker 實在是太香了,而且 Windows 引入 WSL 以後,Docker Desktop 也是秒級啟動,Laravel Sail 也是 base on Docker Compose,是時候跟 Homestead 分手了 。°(°¯᷄◠¯᷅°)°。。

SMB

WSL 有一個痛點,你用他預設掛載進去的目錄執行任何程式碼都慢到哭,目前我實作比較好的方式是透過 SMB 把你的工作目錄掛進去,跑起來跟在本機執行相差無幾,首先是在你要分享的目錄上按右鍵選內容,共用 -> 進階共用 -> 共用此資料夾,共用名稱建議字尾加個 $,這樣在網路芳鄰上會看不到這個目錄,像是 code$,這樣我打 \\HOST\\code$ 可以連到,在公網上是看不到的,另外是我在本機多設定了一組 smb/smb 帳號,這樣我在 demo 的時候也不會因為被看到密碼尷尬,SMB 沒設定的話預設是你這台 Windows 的登入帳號密碼。

WSL

我並沒有使用 Docker Desktop,因為他開機即啟動,而且無法手動 terminate,WSL 資源雖然吃不多,但我沒有要開發的時候並不需要你來吃我這份資源,我知道網路上有許多改機碼之類的方式可以達成這個目的,但官方沒給的我認為都是邪門歪道,我很不喜歡用非官方的方式解決問題,所以我自己起一台 Ubuntu wsl --install -d Ubuntu --name develop,之後再進去自己安裝 Docker,自由自在~

進去 WSL 以後我建議一律 sudo su,用 root 可以少遇到很多奇奇怪怪的權限問題,再來就是手動掛載 smb 了,我將指令放到 ~/.bashrc,因為 WSL 沒有固定 IP,所以你沒辦法把掛載寫死,要的話也是要寫一堆 script 去判斷東西,我是覺得沒必要。

~/.bashrc

# ~/.bashrc - SMB Mount Configuration with Dynamic Windows IP

_get_windows_ip() {
    # 方法 1:透過 /proc/net/route (最穩定)
    # 取得預設閘道,就是 Windows host IP
    local gateway_ip
    gateway_ip=$(awk '$1 == "00000000" {print strtonum("0x"substr($3,7,2))"."strtonum("0x"substr($3,5,2))"."strtonum("0x"substr($3,3,2))"."strtonum("0x"substr($3,1,2))}' /proc/net/route | head -1)
    
    if [[ -n "$gateway_ip" ]]; then
        echo "$gateway_ip"
        return 0
    fi
    
    # 方法 2:透過 ip route (備選)
    gateway_ip=$(ip route show | awk '/^default via/ {print $3}' | head -1)
    
    if [[ -n "$gateway_ip" ]]; then
        echo "$gateway_ip"
        return 0
    fi
    
    # 方法 3:透過 resolv.conf (Windows 10 相容)
    gateway_ip=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf)
    
    if [[ -n "$gateway_ip" ]]; then
        echo "$gateway_ip"
        return 0
    fi
    
    echo "" >&2
    return 1
}

_mount_smb() {
    local smb_share="$1"
    local mount_point="$2"
    local smb_username="smb"
    local smb_password="smb"
    local smb_host
    
    # 檢查必要參數
    if [[ -z "$smb_share" ]] || [[ -z "$mount_point" ]]; then
        echo "Usage: _mount_smb <share> <mount_point>" >&2
        return 1
    fi
    
    # 檢查是否已掛載
    if mountpoint -q "$mount_point"; then
        echo "Already mounted at $mount_point"
        return 0
    fi
    
    # 確保掛載點存在
    [[ -d "$mount_point" ]] || mkdir -p "$mount_point"
    
    # 動態取得 Windows host IP
    smb_host=$(_get_windows_ip)
    
    if [[ -z "$smb_host" ]]; then
        echo "Error: Cannot determine Windows host IP" >&2
        return 1
    fi
    
    echo "Detected Windows host IP: $smb_host"
    echo "Attempting to mount //$smb_host/$smb_share to $mount_point"
    
    # 測試連線
    if ! timeout 3 bash -c "</dev/tcp/$smb_host/445" 2>/dev/null; then
        echo "Error: Cannot connect to $smb_host:445 (SMB port)" >&2
        return 1
    fi
    
    echo "SMB port is reachable, mounting..."
    
    # 執行掛載 (優先使用 SMB 3.0,失敗則試 2.1)
    if sudo mount -t cifs "//${smb_host}/${smb_share}" "$mount_point" \
        -o username="$smb_username",password="$smb_password",vers=3.0,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777 2>/dev/null; then
        echo "✓ SMB share mounted at $mount_point"
        return 0
    else
        echo "SMB 3.0 failed, trying SMB 2.1..."
        
        if sudo mount -t cifs "//${smb_host}/${smb_share}" "$mount_point" \
            -o username="$smb_username",password="$smb_password",vers=2.1,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777 2>/dev/null; then
            echo "✓ SMB share mounted at $mount_point (SMB 2.1)"
            return 0
        fi
        
        echo "✗ Failed to mount SMB share" >&2
        return 1
    fi
}

# 只在互動式 shell 中執行
if [[ $- == *i* ]] && [[ $SHLVL -eq 1 ]]; then
    _mount_smb "code$" ~/mnt/code
fi

這段程式碼簡單講就是把我在本機 share 的 \\HOST\\code$ 掛到 ~/mnt/code,需要多掛幾個多執行幾次 _mount_smb 即可,每次進入 WSL 便抓取 Host Gateway IP 來連結,目錄跟檔案權限都設定成 777 避免一堆怪事發生,反正開發機不怕。

實際測試

這邊獻上幾個環境跑 phpunit 測試結果,測試數量 1364:

Host

real 1m 37.34s
real 1m 37.83s
real 1m 36.08s


平均:1m 37.08s

Vagrant + SMB

real 3m58.906s
real 3m32.119s
real 3m17.965s


平均:3m 36.33s

WSL + SMB

real 1m48.497s
real 1m57.464s
real 1m50.013s


平均:1m51.991s

其實在 Vagrant 裡面即便使用 SMB 執行速度還是悲劇,我已經這樣操作很久了,但因為 Homestead 的便利性一直選擇忽視這個問題,反正也不是整天跑 full test,另外有趣的一件事情,在 WSL 裡面使用 docker build 一個 php 環境跑測試,我預計應該要掉個 3% 的速度,因為它是 Host -> WSL -> Docker Volume,多一層轉換理應要消耗吧,但沒想到跑了幾次的結果它居然跟在 Windows Host 跑的時間差不多,php.ini 除了調整 memory_limit = -1 以外都沒動,不確定是 Docker 預設拉取的 OS 是 Alpine Linux 效能很好,還是 PHP 官方的 container php.ini 的配置有優化,總之蠻意外的。

之後預計會使用 WSL + Docker + Laravel/Sail 開發測試專案啦,謝謝你 Homestead。

沒有留言: