2025/06/02

Neovim Copilot Chat Plugin

Github Copilot 已經是我工作上的好幫手,它的好處是你付一次月租他可以支援所有你有支援的軟體,我安裝在 Jetbrains 全套、vscode、neovim,一魚多吃相當方便,這邊介紹一下 vim/neovim 用來跟 copilot 對話的好用插件 CopilotChat.nvim,當然你要使用之前必須先有 copilot 帳號,以及安裝開通 copilot.vim。

網站中有許多 plugin 安裝的方式,選自己常用的安裝即可,在你的 vim/neovim 配置檔案要加入啟動的語法:

lua << EOF
    require("CopilotChat").setup { }
EOF

" 用 cc 叫出視窗
nnoremap <silent> cc :CopilotChatToggle<CR>

我習慣是盡量不太修改插件預設值,避免有轉換環境的痛苦,套用 cc 叫出視窗是我針對 CopilotChat 唯一的設定了,我們用撰寫一個 docker-compose.yml 當作範例。

nvim docker-compose.yml 建立一個檔案,進入 vim/neovim 按下 cc,可以看到 CopilotChat 視窗在左邊出現。

以下是常用的快捷鍵以及指令:

插入模式 普通模式 功能說明
<Tab> - 觸發或接受補全選單(tokens 自動完成)
<C-c> q 關閉聊天視窗
<C-l> <C-l> 重設並清空聊天視窗
<C-s> <CR> 提交當前的提示內容
- grr 切換目前游標所在行是否為「固定提示」
- grx 清除所有「固定提示」
<C-y> <C-y> 接受最近的一個差異(diff)
- gj 跳轉到最近差異的區塊
- gqa 將所有回覆加入 quickfix 清單
- gqd 將所有差異加入 quickfix 清單
- gy 將最近的差異複製(yank)到暫存器
- gd 顯示原始內容與最近差異的比對
- gi 顯示目前聊天的資訊
- gc 顯示目前聊天的上下文
- gh 顯示說明訊息
指令 說明
:CopilotChat <輸入內容>? 開啟聊天視窗,並可選擇性地輸入問題或指令
:CopilotChatOpen 開啟聊天視窗
:CopilotChatClose 關閉聊天視窗
:CopilotChatToggle 切換聊天視窗(開啟與關閉間切換)
:CopilotChatStop 停止目前的輸出(中斷正在生成的內容)
:CopilotChatReset 重設聊天視窗(清除目前對話)
:CopilotChatSave <名稱>? 儲存聊天記錄,可選擇性指定一個名稱
:CopilotChatLoad <名稱>? 載入先前儲存的聊天記錄
:CopilotChatPrompts 查看並選擇提示範本(例如:解釋代碼、重構等用途)
:CopilotChatModels 查看並選擇可使用的 AI 模型
:CopilotChatAgents 查看並選擇可用的 AI 助手代理(不同風格或角色)
:CopilotChat<PromptName> 使用特定提示範本開啟對話(例如 :CopilotChatExplain)

現在位於左邊的 CC 視窗,輸入寫一份 docker-compose.yml,需要安裝 nginx 以及 php-fpm,先按 esc 回到 visual mode 以後再按下 enter。

我們可以看到他幫我們產生了許多內容,下面包含了基礎的 nginx 配置,更下方還有測試用的 php 程式碼,但我需要的只有 docker-compose 的部分,所以我將 cursor 移到我需要的區塊,然後按下 ctrl + y,此時他便將該區塊的程式碼移到了右邊去。

我接著輸入 prompt,現在的 docker-compose 不需要使用 version,他會依照上文幫我重產內容。

我們可以使用 gd 快速看出差異。

按下 q 回來以後,一樣使用 ctrl + y 將改變套用,這樣就可以套用新的格式,假設你已經脫離上下文,需要透過 cc 給你的檔案建議呢,這時就要用 vim/neovim 的選取功能,將需要他幫忙的地方選取起來,再去問一下 cc。

一樣用 ctrl + y 套用結果,如果你是要他參考整個文件就得使用全選了,另外一種方式是在命令列輸入 :CopilotChat 將 nginx 改為 ubuntu 版本 #buffer,這樣他就會把該檔案的內容當作緩衝使用,我自己是覺得用選的比較快。

另外一個主動參考整個文件的方法就是在第一次打開視窗問問題時,加上 #buffer,例如:

#buffer 將 $name 取代為 $userName

這樣他便會去直接參考整個檔案。

當我們使用 :CopilotChatModels 時,他會列出所有你可以使用的 model,可以記一下編號,移動到最下面後可以填入該編號,便可以切換模型。

但這個變動只有在這次有效,要永久使用該模型可以設定 config。

lua << EOF
require("CopilotChat").setup {
    model = 'claude-3.7-sonnet'
}
EOF

2025/05/26

Homestead SSL Issue

Laravel Homestead 在使用 nginx 創建網頁 config 時是有幫他配置 ssl key 的,但為什麼我們使用 https 還是會被警告呢,因為我們的瀏覽器是不認這個憑證的,在 Windows 上可以手動安裝該憑證讓你測試的時候可以透過 https 連到你的測試機,有時候開發會遇到跨網站連結 https 的狀況,如果你是 http 的話可能會被擋下來。

配置 Homestead.yml

folders:
  - map: ./ssl
    to: /ssl
  - map: ./code
    to: /home/vagrant/code

sites:
  - map: ssl.test
    to: /home/vagrant/code/
    php: "8.2"

這是我添加的測試網站,將根目錄開一個 ssl 目錄,在 code 目錄下放一個有 echo hello world 的 PHP 檔案,修改本機的 hosts 將 ssl.test 指到該 ip。

vm 起來後,vagrant ssh 進去該 vm,執行 sudo cp /etc/ssl/certs/ca.homestead.homestead.pem /ssl,將憑證複製到 /ssl 下,exit 退出 vm。

前往該 ssl 目錄,執行以下步驟:

  1. 右鍵 點擊憑證檔,選擇 「安裝憑證」
  2. 點選 「下一步」
  3. 選擇 「將所有憑證放入以下的存放區」
  4. 點選 「瀏覽」
  5. 選擇 「受信任的根憑證授權單位」
  6. 點選 「下一步」
  7. 點選 「完成」

此時瀏覽 https://ssl.test 應該就不會有警告了,如果還是有的話可能是 nginx cache,重開 server 或瀏覽器應該就解決了,可以先用無痕驗證一下。

該憑證過期時間為十年,應該是蠻夠用的,過期的話要重產一個新的,但我想應該在過期之前就會重置測試機了。

Homestad PHP PPA Issue

最近執行 Homestead 的 vagrant reload --provision 會遇到錯誤。

E: Repository 'https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy InRelease' changed its 'Label' value from '***** The main PPA for supported PHP versions with many PECL extensions *****' to 'PPA for PHP'

這是因為該 PPA 的 Label 產生了變化,解決方法也挺簡單。

vagrant ssh 進去 vm 後,執行下面指令:

sudo rm -rf /var/lib/apt/lists/*
sudo apt-get clean
sudo apt-get update --allow-releaseinfo-change

記得遇到這個問題處理過後要退出 vm 執行 vagrant reload --provision,才會有變動。

2025/05/13

Tmux Auto Split Window Syntax

tmux 是很強大的視窗分割工具,他可以透過語法建立初始化的視窗結構,今天的案例是我在我的 vagrant 虛機裡面有許多 laravel 專案,每個專案都有使用 horizon queue,每次要工作的時候我先 vagrant ssh 進去虛機以後,再手動割四個視窗,進入四個專案目錄後執行 php artisan horizon 啟動 queue server,這個過程可以透過語法讓 tmux 自己幫你完成。

horizon.sh

#!/bin/bash

# API 專案目錄
PROJECTS=(
    "/project1"
    "/project2"
    "/project3"
    "/project4"
)

# 取得目前 tmux Session 名稱
SESSION=$(tmux display-message -p '#S')

# 開啟新視窗並處理第一個 pane
FIRST_DIR="${PROJECTS[0]}"
tmux new-window -n "horizon" -c "$FIRST_DIR"
tmux select-window -t "${SESSION}:horizon"

# 設定第一個 pane 並執行命令
tmux select-pane -t 0
tmux send-keys -t 0 "php artisan horizon" C-m

# 處理其餘 panes
for i in "${!PROJECTS[@]}"; do
    if [ "$i" -eq 0 ]; then continue; fi

    DIR="${PROJECTS[$i]}"

    # 分割方向
    if [ "$i" -eq 1 ]; then
        tmux split-window -h -c "$DIR"
    else
        tmux select-pane -t $((i - 1))
        tmux split-window -v -c "$DIR"
    fi

    # 執行命令
    tmux select-pane -t "$i"
    tmux send-keys -t "$i" "php artisan horizon" C-m
done

# 整理 pane 排列
tmux select-layout tiled

該 script 需要在開啟了一個 tmux window 後執行。

2025/05/02

Log Handle In Docker

Docker 現在已經是可以當 production server 用的工具了,來聊一下他對 logs 有哪些配置方式。

根據 docker 官方文件,目前有以下幾個配置方法:

Driver 說明
none 容器沒有可用的日誌,且 docker logs 不會返回任何輸出。
local 日誌以一種為了最小化開銷而設計的自定義格式儲存。
json-file 日誌格式為 JSON。Docker 的預設日誌驅動程式。
syslog 將日誌消息寫入 syslog 設施。主機上必須運行 syslog 守護程序。
journald 將日誌消息寫入 journald。主機上必須運行 journald 守護程序。
gelf 將日誌消息寫入 Graylog 擴展日誌格式 (GELF) 端點,如 Graylog 或 Logstash。
fluentd 將日誌消息寫入 fluentd(轉發輸入)。主機上必須運行 fluentd 守護程序。
awslogs 將日誌消息寫入 Amazon CloudWatch Logs。
splunk 使用 HTTP 事件收集器將日誌消息寫入 Splunk。
etwlogs 將日誌消息寫入 Windows 事件追蹤 (ETW) 事件。僅適用於 Windows 平台。
gcplogs 將日誌消息寫入 Google Cloud Platform (GCP) Logging。

如果你能擁有自己的 log server,會建議把 log 打進 log api server,擁有權限的人就可以在 web tool 上做所有的查詢以及決定資料怎麼保留跟處理,畢竟如果 log 停留在各 server,以現在很多服務走 HA 或是 k8s 情況下,遇到狀況你到底要進哪台服務查詢都會成為問題。

不過現實生活中很多人都只有一台 server,而且也沒有 api log server 這種資源,只是要有 log 即可的話,我們一般會用 json-file 這個選項,這也是 docker 預設的 log driver。

docker-compose json-file 範例

services:
    nginx:
        image: nginx
        container_name: nginx
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
        logging:
            driver: json-file
            options:
                max-size: "10m"
                max-file: "3"

這個範例會把 nginx 的 log 儲存到 /var/lib/docker/containers/<container-id>/<container-id>-json.log,當 log 檔案大小超過 10MB 時,docker 會自動將舊的 log 檔案重新命名為 <container-id>-json.log.1,並開始寫入新的 log 檔案,最多會保留 3 個 log 檔案,當你使用指令 docker logs 時,docker 會自動讀取最新的 log 檔案,並顯示在終端機上。

一般 image 如 nginx,他會把 log 設定成 stdout,這樣 docker 才能夠讀取到 log,然後寫入到你設定的 log driver 裡面,選擇 json-file 時 docker 會自動幫你把 stdout 或 stderr 的 log 轉成 json 格式,然後寫入到你設定的 log driver 裡面。

nginx 的話,他的配置會是這樣:

access_log /dev/stdout;
error_log /dev/stderr;

這樣的缺點是,access log 會跟 error log 都寫在同一個檔案裡面,會造成你在查詢 log 的時候不方便,假設我們想要使用 docker,又想要產生跟傳統 web server 一樣的 log,我們可以修改 nginx.conf,讓他把 log 寫到 /var/log/nginx/access.log 和 /var/log/nginx/error.log。

Nginx Config 範例

nginx.conf

http {
    server {
        listen       80;
        server_name  localhost;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
    }
}

然後把 log 目錄掛載出來。

docker-compose.yml

services:
    nginx:
        image: nginx
        container_name: nginx
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
            - ./logs:/var/log/nginx

我們在 /etc/logrotate.d/nginx 新增一個檔案,然後把以下的內容貼上去:

logrotate conf

/var/log/nginx/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec nginx nginx -s reopen
    endlaction
}

docker exec nginx nginx -s reopen 的用意是 logrotate 在 rotate 之後需要呼叫 docker 重新開啟 nginx 的 log,不這樣的話 nginx in docker 會使用你 rename 後的 log 檔案繼續寫 log。

當我們使用 compress,並且會針對壓縮過後的檔案做後續處理的話,一定要有 sharedscripts 以及使用 lastaction,如果使用 postrotate 的話,他不會等所有的檔案都執行完壓縮就直接作動了。

這邊有幾個 logrotate 的指令可以使用:

指令 說明
prerotate/endscript 在進行日誌輪轉之前執行的指令,每個匹配的日誌檔案都會執行一次
postrotate/endscript 在進行日誌輪轉之後執行的指令,每個匹配的日誌檔案都會執行一次
firstaction/endscript 在所有匹配日誌檔案進行輪轉之前執行的指令,只執行一次
lastaction/endscript 在所有匹配日誌檔案進行輪轉之後執行的指令,只執行一次
preremove/endscript 在舊的日誌檔案被移除之前執行的指令

另外有一種不用重新開啟檔案的配置參數 copytruncate,這個參數會在 logrotate 開始 rotate 的時候複製原本的 log 檔案,然後 truncate 原本的 log 檔案,這樣 nginx 就不需要重新開啟檔案了,但這樣有機會漏寫紀錄,所以不建議使用這個參數。

Apache2 Config 範例

apache2 的原始 log 配置會是:

ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 common

我們要修改成:

ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined

然後把 log 目錄掛載出來。

docker-compose.yml

services:
    apache2:
        image: httpd
        container_name: httpd
        ports:
            - "80:80"
        volumes:
            - ./httpd.conf:/usr/local/apache2/conf/httpd.conf
            - ./logs:/var/log/apache2

httpd.conf

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot /var/www/html

    ErrorLog /var/log/apache2/error.log
    CustomLog /var/log/apache2/access.log combined
</VirtualHost>

logrotate conf

/var/log/apache2/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec apache2 apachectl -k graceful
    endscript
}

這個範例跟 nginx 的範例一樣,只是把 nginx -s reopen 改成 apachectl -k graceful。

假設你 logrotate 後要執行其他 bash 做其他處理的話。

/var/log/apache2/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec httpd apachectl -k graceful
        /usr/bin/bash /usr/local/bin/other_script.sh
    endscript
}

2025/04/02

Neovim Installation

neovim 是我即便是在 windows 下也會開來使用的 command line editor,來 memo 一下 Windows 跟 Linux 怎麼安裝。

Windows

我在 Windows 上都是使用 scoop 來管理套件,所以 neovim 也是用 scoop 來安裝,這樣好處是我用 scoop update 時都可以保持拿到最新的 neovim。

scoop install neovim

powershell 可以執行指令安裝 vim-plug。

iwr -useb https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim | ni $HOME\AppData\Local\nvim\autoload\plug.vim -Force

接下來就是配置你的個人檔案,位置在 C:\Users\你的使用者名稱\AppData\Local\nvim\init.vim,可以使用任何編輯軟體包括 neovim 開啟他,把 config 都配好以後,執行 neovim,首次執行會有很多錯誤是正常的,因為你的 plugin 並沒有真的安裝,開啟後執行 :PluginInstall,關閉再開啟 neovim 應該就正常了。

Linux

Linux 的部分有兩種方法,一個是透過你自己的 OS 去找最新的 repo 安裝,另一個是可以去 https://github.com/neovim/neovim/releases/,neovim 的 github 官網找最新版本的 appimage 檔案,只要你的 OS 有 FUSE 就可以直接使用,以 ubuntu 22 為例,此文章撰寫時最新的穩定版本是 NVIM v0.11.0。

curl -LO https://github.com/neovim/neovim/releases/download/v0.11.0/nvim-linux-x86_64.appimage
chmod +x nvim-linux-x86_64.appimage
sudo mv nvim-linux-x86_64.appimage /usr/local/bin/nvim

執行完這段就有 nvim 可以執行了,接著裝套件。

sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
       https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'

mkdir -p ~/.config/nvim
touch ~/.config/nvim/init.vim
nvim ~/.config/nvim/init.vim

接著就跟 Windows 一樣安裝套件後就可以用了,之後要更新 neovim 版本只要抓最新版本的 appimage 替換即可。

2025/03/03

Pyinstaller With Tobix Pywine

Python 可以透過 PyInstaller 將執行檔編譯成 Windows 可以使用的 exe,很好用,不同的 python 版本編譯出來的 exe 對目前的 Windows 有不同的支援度:

Python 版本 Windows 7 Windows 8 Windows 10 Windows 11
Python 3.6 ✅ (最後支援) ✅ ✅ ✅
Python 3.7 ✅ ✅ ✅ ✅
Python 3.8 ✅ ✅ ✅ ✅
Python 3.9 ✅ ✅ ✅ ✅
Python 3.10 ⚠️ (可能有問題) ⚠️ (可能有問題) ✅ ✅
Python 3.11 ❌ (不支援) ❌ (不支援) ✅ ✅
Python 3.12 ❌ (不支援) ❌ (不支援) ✅ ✅

所以編譯的時候要考量 python 的版本,Pyinstaller 只有支援 Windows,在 Linux like 的環境要執行必須透過 Wine 這種指令驅動,如果你要跑 CI/CD 沒那麼方便,後來我選擇了 tobix/pywine - Docker Image | Docker Hub 這個 docker 專案,只要選擇你要的 python 版本,如 tobix/pywine:3.8 ,將外部的 python 檔案 mount 進去 container,執行 docker exec -it builder wine pyinstaller.exe -F xxx.py 便可以編譯了,如果有相關要安裝的套件,也可以事先執行 docker exec -it builder wine python.exe -m pip install -r /tmp/requirements.txt。