我在做官方的模組開發新手教學時,發現開發模組會常需要停掉 odoo,然後重啟並加上 -u estate
參數,來更新我們剛剛修改的模組,讓 ORM 應用我們剛剛修改的資料庫 schema;或者加上 --dev xml
讓我們在修改 XML
後只要重新整理網頁就能看到最新的變更。
然而,在我們搭建的環境中,要實現這樣的重新啟動需要:
odoo
容器。docker-compose
的 odoo
設定中加入 CMD
,值為 odoo -u estate
(如果有閱讀之前關於 entrypoint.sh
的章節,應該會了解為什麼只需這樣設定就能帶入完整的 Odoo 啟動參數)。docker-compose up
重新啟動 odoo
容器。我覺得這樣的流程實在太繁瑣,大大降低了我繼續進行新手教學的意願(沒錯我到現在還沒完成 Orz)。所以我決定尋找一個更簡便的方法來解決這個問題。
一開始,我嘗試在 entrypoint.sh
中加入類似前一章自動初始化資料庫的機制,增加以下的段落:
if [[ "$1" == "restart" ]]; then
echo "$HEADER Stopping existing odoo process gracefully..."
# 找到正在執行的 odoo 程序的 PID
PIDS=$(pgrep -f "^[p]ython3 $ODOO_BIN")
if [ -z "$PIDS" ]; then
echo "$HEADER No running odoo process found."
exit 1
fi
PID_COUNT=$(echo "$PIDS" | wc -w) # 計算找到的 PID 數量
if [ "$PID_COUNT" -gt 1 ]; then
echo "$HEADER Multiple odoo processes found (PIDs: $PIDS). Aborting to prevent accidental termination."
exit 1
fi
PID=$PIDS
echo "$HEADER Found odoo process with PID: $PID"
kill $PID # 發送 SIGTERM 信號,進行 Graceful Shutdown
# 等待 Graceful Shutdown
echo "$HEADER Waiting for odoo process to stop..."
TIMEOUT=30 # 超時時間(秒)
ELAPSED=0
while kill -0 $PID 2>/dev/null; do
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "$HEADER Failed to stop odoo process $PID within timeout."
exit 1
fi
sleep 1
ELAPSED=$((ELAPSED + 1))
done
echo "$HEADER odoo process $PID has been stopped."
shift # 移除 'restart' 參數
echo "$HEADER Starting odoo with new parameters."
echo "$HEADER Executing: $ODOO_BIN $@ ${DB_ARGS[@]}"
exec $ODOO_BIN "$@" "${DB_ARGS[@]}"
fi
這樣我就能在 Odoo 容器運行的狀態下,透過在 host 執行 docker-compose exec odoo /entrypoint.sh odoo restart -u estate
,使用同一個 entrypoint.sh
來達成目的。
然而,經過一番嘗試後,我發現我忽略了一個 Docker 的重要特性——只要 PID 1(也就是容器內的第一個程序)停止,整個容器就會關閉,這個腳本的後半部分也無法執行。如果要解決這個問題,就必須大幅修改 Docker 的執行方式,讓容器內有其他常駐的程序作為 PID 1,並透過它來管理 odoo 的運行。
考慮到這樣的修改實在過於繁瑣,我最終放棄了這個方案。
第二個解決方案是撰寫一個小腳本——odoo-restart.sh
,從 host 執行這個腳本來解決問題。
# 顯示使用說明的 Function
usage() {
echo "Usage: $0 [odoo command parameters]"
echo "Example:"
echo " $0"
echo " $0 -u estate"
echo " $0 -u estate --dev xml"
exit 1
}
# 檢查是否請求顯示幫助內容
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage # 如果提供了 -h 或 --help 參數,則顯示使用說明
fi
# 第一步:停止 odoo 容器
echo "Stopping odoo container..."
docker-compose stop odoo # 停止 odoo 容器
if [ $? -ne 0 ]; then # 檢查停止是否成功
echo "Error: Failed to stop odoo container." # 如果停止失敗,顯示錯誤
exit 1
fi
# 第二步:根據參數生成 docker-compose.override.yml 文件
# 如果提供了參數,將其作為 odoo 的啟動指令
if [ "$#" -gt 0 ]; then
echo "Creating docker-compose.override.yml with the following command: odoo $@" # 顯示將要創建的指令
CMD="odoo $@"
else
echo "Creating docker-compose.override.yml with the default command: odoo" # 如果沒有提供參數,使用預設的 odoo 指令
CMD="odoo"
fi
# 將生成的指令寫入 docker-compose.override.yml 檔案
cat > docker-compose.override.yml <<EOF
services:
odoo:
command: $CMD
EOF
# EOF 是這段檔案的結尾標記,用於標識多行輸入的結束
# 第三步:使用新的設定啟動 odoo 容器
echo "Starting odoo container with updated command..."
docker-compose up -d odoo # 使用更新的指令啟動 odoo 容器
if [ $? -ne 0 ]; then # 檢查啟動是否成功
echo "Error: Failed to start odoo container." # 如果啟動失敗,顯示錯誤
# 在退出前刪除 docker-compose.override.yml 文件
rm -f docker-compose.override.yml
exit 1
fi
echo "Waiting for odoo to initialize..."
sleep 10 # 等待 10 秒以讓 odoo 完成初始化
# 第四步:移除 docker-compose.override.yml 檔案
echo "Removing docker-compose.override.yml..."
rm -f docker-compose.override.yml # 移除臨時的 override 檔案
echo "odoo has been restarted with the specified parameters." # 完成並顯示成功訊息
關於 docker-compose.override.yml:
docker-compose.override.yml
是 Docker Compose 提供的一種機制,允許在不修改原始 docker-compose.yml
的情況下,臨時覆蓋或添加配置。這對於開發環境特別有用,我們可以根據需要動態調整設定,而不影響版本控制中的原始設定。
最終這個小腳本的測試結果符合預期。開發時如果需要重啟並加參數,只需執行類似 ./odoo-restart.sh -u estate
的指令,就能簡單有效地達成目的。