生產環境常見場景:Tomcat 以普通用戶(如 chb)運行,Web 頁面提供「關機」按鈕呼叫 sudo /sbin/poweroff。配置好 /etc/sudoers 並設定開機自啟後,發現開機自啟的 Tomcat 無法關機,但手動重啟 Tomcat 後卻正常。本文深入分析 tty 與 sudo requiretty 的關係,給出徹底解決方案。
// Java 代碼
Runtime.getRuntime().exec("sudo /sbin/poweroff");
# /etc/sudoers 尾部
chb ALL=NOPASSWD:/sbin/reboot,/sbin/poweroff
# /etc/rc.d/rc.local 開機自啟
su chb -c "/opt/tomcat/bin/startup.sh"
現象: 開機後網頁點關機無反應;SSH 登入殺掉 Tomcat 進程、手動啟動後,關機按鈕恢復正常。
rc.local 在系統啟動早期由 init/systemd 拉起,標準輸入/輸出/錯誤皆重導向至 /dev/null,沒有分配 tty(ps -p -o tty= 顯示 ?)。/etc/sudoers 預設含 Defaults requiretty,意即「只有擁有控制終端的進程才能執行 sudo」。pts/X 偽終端,此時 sudo 檢查通過。# 查看當前進程 tty
ps -p $$ -o tty=
# 開機自啟 Tomcat 進程
ps -ef | grep tomcat | grep -v grep
ps -p -o tty= # 輸出 ?
# 手動啟動後
ps -p -o tty= # 輸出 pts/0 等
# visudo
# 註釋或刪除該行
# Defaults requiretty
影響: 允許無終端進程(cron、systemd service、rc.local)執行 sudo。安全風險可控,因 sudoers 已限制具體命令與用戶。
# visudo
Defaults:chb !requiretty
# 或針對命令
Defaults!/sbin/poweroff !requiretty
更精細的權限控制,保留全域 requiretty 防禦惡意腳本。
# /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat
After=network.target
[Service]
Type=forking
User=chb
Environment=JAVA_HOME=/usr/lib/jvm/java-11
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
PIDFile=/opt/tomcat/temp/tomcat.pid
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
systemd 服務可透過 StandardInput=tty-force 或 TTYPath=/dev/tty0 顯式分配 tty,或直接配合方案二豁免,更優雅地管理生命週期、日誌與重啟策略。
systemctl poweroff(需 sudo systemctl 權限)替代 /sbin/poweroff,統一由 systemd 管理電源狀態。/var/log/secure 監控 sudo 使用記錄。理解 tty 與 sudo 的交互機制,能讓您在部署自動化腳本、容器化遷移、系統服務管理時避開大量「明明配置正確卻不生效」的坑。