# 🔁 以服務方式執行 安裝程式會建立並啟用 `systemd` 服務。本頁說明服務設定、操作命令,以及啟動失敗或重載遭拒時的檢查方式。 ## 🧾 unit 做了什麼 ```bash systemctl cat pingclair ``` 重要的設定鍵如下: ```text [Service] Type=notify NotifyAccess=main User=pingclair Group=pingclair AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE Environment="RUST_LOG=info" ExecStart=/usr/local/bin/pingclair run /etc/Pingclair/Pingclairfile ExecReload=/bin/kill -USR1 $MAINPID WorkingDirectory=/var/lib/pingclair Restart=on-failure RestartPreventExitStatus=1 RestartSec=5s LimitNOFILE=1048576 LimitNPROC=512 ProtectSystem=full PrivateTmp=true NoNewPrivileges=true ``` 依序來看: - `Type=notify` 與 `NotifyAccess=main`:伺服器在監聽器綁定完成時主動通知 `systemd`,因此 `systemctl start` 會等待監聽器就緒後才返回。 - `User=pingclair` 搭配 `AmbientCapabilities=CAP_NET_BIND_SERVICE`:伺服器以非特權身分執行,仍然可以綁定 80 與 443 連接埠。 - 這裡刻意沒有 `PINGCLAIR_TLS_STORE`。服務帳號的家目錄是 `/var/lib/pingclair`,憑證自然落在 `/var/lib/pingclair/.local/share/pingclair`:這是二進位檔的預設值、安裝程式建立並遷移進去的目錄,也是 `pingclair environ` 印出的路徑。再多指定一個儲存區,等於替一個已有答案的問題硬塞第二個答案。 - unit 不使用 `ExecStartPre` 執行 `validate`,因為 `RestartPreventExitStatus=` 只適用於主行程,無法阻止失敗的前置命令被持續重試。伺服器會在綁定監聽器前編譯設定;無效設定以結束碼 1 終止,讓 unit 保持失敗狀態。 - `ExecReload` 送出 `SIGUSR1`,伺服器把這個訊號視為「重新讀取檔案」。`SIGHUP` 會被刻意忽略;曾有 unit 送出它,回報成功,舊設定卻繼續在提供服務([issue #66](https://github.com/dorianverlaine/pingclair/issues/66))。由於 `systemd` 只能看到 `kill` 結束了,伺服器會把它對檔案的處理結果發布在這個 unit 的狀態列上——`Serving (reloaded 1 listener(s) in 323.341µs)` 或 `Reload rejected: …`——`systemctl status` 會顯示出來。完整說明見下方的[重載一節](#-重載意味著什麼)。 - `Restart=on-failure` 搭配 `RestartPreventExitStatus=1` 與 `RestartSec=5s`:結束碼 1 代表設定或憑證儲存區完全無法使用,所以 unit 會停在 `failed`,等維運人員查看,而不是每五秒重試一次。其他任何失敗都會重啟。 - `ProtectSystem=full`、`PrivateTmp`、`NoNewPrivileges`、`LimitNPROC` 與 `LimitNOFILE`:伺服器只拿到它需要的檔案系統視野與行程限制,僅此而已。 兩種安裝路徑寫出的都是同一個檔案。一行式安裝內嵌了 `scripts/pingclair.service` 逐位元組相同的副本——兩者一旦不同,`just repo-lint` 就會失敗——所以全新的 `curl | bash` 安裝與從原始碼 checkout 安裝會產生相同的 unit,而且在兩種路徑上,`systemd-analyze verify /etc/systemd/system/pingclair.service` 都不會對這個 unit 提出任何問題。 ## 🎛️ 操作服務 `pc service` 包裝了針對這個 unit 的 `systemctl`,兩者可以互換: | 工作 | 使用 `pc` | 使用 `systemctl` | | --- | --- | --- | | 啟動 | `sudo pc service start` | `sudo systemctl start pingclair` | | 停止 | `sudo pc service stop` | `sudo systemctl stop pingclair` | | 重載設定 | `sudo pc service reload` | `sudo systemctl reload pingclair` | | 監聽器或全行程層級的變更後重啟 | `sudo pc service restart` | `sudo systemctl restart pingclair` | | 狀態 | `pc service status` | `systemctl status pingclair` | | 追蹤日誌 | — | `journalctl -u pingclair -f` | `pc service status` 會印出 unit 自己的視角,包括伺服器送出的就緒狀態: ```text ● pingclair.service - Pingclair High-Performance Web Server Loaded: loaded (/etc/systemd/system/pingclair.service; enabled; preset: enabled) Active: active (running) since Tue 2026-09-22 05:57:21 UTC; 18s ago Docs: https://pingclair.com/start/service/ Main PID: 27630 (pingclair) Status: "Serving" ``` ## 🔁 重載意味著什麼 修改過的 `/etc/Pingclair/Pingclairfile` 透過一個訊號送達執行中的伺服器,有兩個命令會送出它。 `SIGUSR1` 就是重載訊號,它本身不需要任何設定: ```bash sudo kill -USR1 "$(systemctl show -p MainPID --value pingclair)" ``` `pc service reload`——或是完全相同的 `sudo systemctl reload pingclair`——會替你送出這個訊號。unit 的 `ExecReload` 是 `/bin/kill -USR1 $MAINPID`,所以最直覺的那個命令現在就是能用的那個;過去送出 `SIGHUP` 的 unit 會回報成功卻什麼都沒套用,這正是 [issue #66](https://github.com/dorianverlaine/pingclair/issues/66) 記錄的問題。 `pingclair reload` 透過 Admin API 走到同一段程式碼,並回報伺服器對檔案的判定,因此需要全域選項區塊裡的 `admin` 選項: ```text ✅ Configuration reloaded successfully ``` ```text Error: ❌ Reload failed (400): HTTP/1.1 400 Bad Request ``` `systemctl reload` 只能回報一件事:`kill` 送達了訊號。伺服器是在那之後才讀取檔案,所以判定結果會改送到 unit 的狀態列與 journal。`pc service reload` 會照實這樣說,而不是宣稱設定已經套用: ```text $ sudo pc service reload ✅ Reload signal delivered to pingclair.service ℹ️ The result lands a moment later: `systemctl status pingclair` or `journalctl -u pingclair -n 20` $ systemctl status pingclair --no-pager | grep Status Status: "Serving (reloaded 1 listener(s) in 323.341µs)" ``` 執行中的伺服器無法套用新檔案的要求時,舊設定會繼續提供服務,狀態列會指出哪一項變更被拒絕。最常見的情況是把網站從 `:80` 搬到 `:8080`,因為監聽器拓撲在啟動時就連同 socket 一起定型了: ```text Status: "Reload rejected: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together" ``` 不論走哪條路,無法編譯的設定都只會讓舊設定繼續跑,網站照常回應。請先驗證: ```bash sudo pingclair validate /etc/Pingclair/Pingclairfile ``` 執行中的行程無法吸收的變更屬於例外。啟動時固定的全域政策(例如 `trusted_proxies`)變更會被重載拒絕,必須重啟;行程日誌設定可重載。請使用`sudo pc service restart`。新增或搬移監聽器的設定也一樣——狀態列會列出新增與移除的位址——因為重載只更新政策,不換監聽 socket。 ## 🛑 停止意味著什麼 `systemctl stop` 送出 `SIGTERM`。伺服器先讓 `/ready` 回應 `503`,停止接受新請求,再等待執行中的請求完成,最長為 `grace_period`(預設 30 秒)。期限到達時關閉仍存活的 QUIC 連線。重啟在兩個行程之間會有連線空窗;只改網站政策時優先重載。 ## 📜 日誌 unit 設定了 `RUST_LOG=info`,並把所有輸出送到 journal: ```bash sudo journalctl -u pingclair -f sudo journalctl -u pingclair --since '10 min ago' ``` 啟動、重載、憑證作業,以及每個請求一行的存取紀錄都會出現在那裡: ```text INFO pingclair::run: 📄 Loaded configuration from: /etc/Pingclair/Pingclairfile INFO pingclair::run: 🔔 Received SIGUSR1, reloading configuration from: /etc/Pingclair/Pingclairfile INFO pingclair::run: ✅ Configuration reload completed successfully in 323.341µs INFO pingclair::run: 📊 1 listener(s) updated INFO pingclair_proxy::server: 📝 Access request_id="65c09fa25d457-6" method="GET" host="localhost" path="/" status=200 bytes=18747 duration_ms=0 remote_ip=::1 user_agent="curl/8.18.0" ``` 伺服器拒絕的重載也會以同樣方式記錄,附上原因,並註明沒有任何變更: ```text ERROR pingclair::run: ❌ Configuration reload rejected after 414.491µs: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together kind=RestartRequired ERROR pingclair::run: 💡 Previous configuration remains active, unchanged ``` 若要一份獨立、可輪替的日誌,請設定 `log` 輸出,寫到 `/var/log/pingclair` 底下;這個目錄由安裝程式建立,並交給服務使用者擁有。 ## ⚠️ 服務啟動失敗時 - **`is-active` 顯示 `activating`,`NRestarts` 不斷增加。** 舊版 unit 的 `Restart=always` 或 `ExecStartPre` 驗證命令可能造成持續重試。目前的 unit 使用 `Restart=on-failure`、`RestartPreventExitStatus=1`,且不執行前置驗證。請先執行 `sudo systemctl stop pingclair`,更新 unit 並修正設定,再執行 `sudo systemctl reset-failed pingclair`。 - **`Job for pingclair.service failed because the control process exited with error code`。** 伺服器在綁定任何東西之前就拒絕了設定,編譯器給的理由在 journal 裡,例如 ``Error: ❌ Configuration Error: Compile error: Unsupported feature: `encode br`: Brotli is not implemented for proxied responses; use `encode zstd gzip` ``。 - **`TLS store /var/lib/pingclair/.local/share/pingclair is not writable: Permission denied`。** 儲存區屬於服務帳號。請檢查 `sudo ls -ld /var/lib/pingclair/.local/share/pingclair`;擁有者應該是 `pingclair`。 - **`systemd-analyze verify` 對已安裝的 unit 回報 `Missing '=', ignoring line`。** 舊版的一行式安裝寫出的 unit,其註解被 shell 展開了——變成 25 行 `--help` 輸出,`systemd` 會忽略它們。用目前的安裝程式重新安裝,就會原封不動地寫出 unit,這則回報也會消失。 - **unit 在執行,卻沒有任何回應。** 監聽器已綁定,請求卻沒有抵達。請依[安裝頁面](/zh-TW/start/install/)的說明,先檢查供應商的防火牆,再檢查主機本身的。 ## 🧭 下一步 - [升級與移除](/zh-TW/start/upgrade/):重新執行會保留什麼,以及如何把它全部移除。 - [HTTPS](/zh-TW/start/https/):憑證,包括儲存區的位置,以及 `pingclair trust` 為什麼需要 `PINGCLAIR_TLS_STORE`。 - [`log`](/zh-TW/reference/directives/#log):本頁從 journal 讀取的存取日誌輸出。