Hamdy Tabsissi
繁體中文

這個網站 正式上線 個人專案

由我維運、可即時查證的完整系統

您正在瀏覽的網站,運行在一台由我親自設定、強化資安、更新與監控的伺服器上,從一台空機器一路到這個頁面。

需求

作品集應該證明能力,而不只是列出能力。如果把它放在代管平台上,反而會把我最想展示的東西藏起來:長期維運一個穩定又安全的服務。

構想

讓主機本身成為展示:這裡寫的每一件事,都能在提供這個頁面的同一台機器上即時驗證。

我打造了什麼

  • 位於巴黎區域的雲端伺服器,花了好幾天、試了 6 次才終於取得,因為那裡的免費資源非常稀少。
  • 完整的網站服務架構,每個服務都在各自受限的空間中執行,單一服務出狀況也不會波及其他服務。
  • 從第一晚就開始強化安全:一個晚上套用 7 項措施,過程中服務完全沒有中斷,之後再逐步持續推進。
  • 自動更新:通過核准的變更會自動上線並經過健康檢查,如果新版本沒有回應,就會自動回滾。
  • 資料庫持續備份,另外每晚建立快照,並依日、週、月分層保存。
  • 一個獨立的探測程式,每小時檢查網站資安並公開結果,就顯示在下方。

成果

  • 5由獨立探測程式每小時重跑的資安檢查
  • 7一晚內完成,零停機
  • 6取得伺服器前嘗試的次數
  • 1 小時兩次獨立資安檢查之間的間隔

自 2026 年 8 月起,它與 EventMap 一起運行,提供這個作品集網站。下方的即時檢查結果由獨立的探測程式量測,而不是手動填寫。

即時檢查

  • TLS 已驗證
  • 原始伺服器 已驗證
  • 標頭 已驗證
  • DNSSEC 已驗證
  • 可用性 已驗證

這些檢查由獨立的探測程式每小時透過網站的公開介面量測——並非手動填寫。 最近一次執行: 五項檢查的詳細說明(法文)

畫面一覽

這個網站的首頁:星空背景上寫著「我將需求轉化為實用的系統」,旁邊有一個小小的環繞軌道系統
這個網站的首頁:星空背景上寫著「我將需求轉化為實用的系統」,旁邊有一個小小的環繞軌道系統

我學到了什麼

絕不能只在機器本身上測試:疊了兩層防火牆時,服務在本機可能回應得好好的,網際網路上卻沒有人連得到。另外,懂得選擇不安裝某些東西(這次是資料庫伺服器),也是一種維運能力。

技術細節 技術堆疊、架構與決策——給技術背景的讀者

技術堆疊

  • Ubuntu 22.04
  • Oracle Cloud ARM (Always Free)
  • nginx
  • Node.js + Express
  • SQLite (node:sqlite)
  • systemd
  • fail2ban
  • iptables
  • Cloudflare
  • GitHub Actions
  • Litestream + restic

架構一覽

  1. 訪客的瀏覽器
  2. Cloudflare — TLS 與快取
  3. nginx — 安全標頭、速率限制
  4. Node.js + Express — 在伺服器端產生頁面,支援 7 種語言;以沙箱隔離的 systemd 服務
  5. SQLite (node:sqlite) — 專案資料與第一方造訪紀錄
  6. 周邊機制:GitHub Actions 測試每一次變更;VM 拉取通過驗證的版本、檢查 /health,失敗時自動回滾;資安觀測站每 30 分鐘重新檢查一次資安。

運作方式

  • 4 OCPU / 24 GB 的虛擬機(VM,Ubuntu 22.04)。nginx 反向代理位於各個隔離的服務之前,每個服務都以不具 shell 的專用系統帳號執行,並設定 ProtectSystem=strict 與 NoNewPrivileges。
  • 第一個晚上(2026 年 8 月 23 日):重新產生管理員密碼、在 SSH 上啟用 fail2ban、嚴格的安全標頭(CSP、HSTS、X-Frame-Options)、存取紀錄中 IP 位址的最後一段歸零、限制 journald 的保存期限、nginx 紀錄檔輪替,以及將機密資料加密備份並存放在 VM 之外。
  • 接著:nginx 速率限制、停用 rpcbind、設定 swap、遠端桌面只綁定 127.0.0.1(僅能透過 SSH 通道連線)、停用 SSH 密碼驗證、以 Full (strict) 模式使用 Cloudflare Origin CA 憑證。
  • 拉取式(pull-based)持續部署:推送到 master 的變更先由 GitHub Actions 驗證,再移動 prod 標籤;VM 透過 systemd 計時器拉取該版本、執行健康檢查,失敗時自動回滾。不開放任何對內連接埠,GitHub 上也不存放任何 VM 的登入憑證。
  • 兩層疊加的防火牆(VM 上的 iptables 加上雲端的 Security List),這也是我從不只在機器本身上測試的原因。
  • 刻意的取捨:安全性更新會自動安裝,但 VM 絕不會自行重新開機或升級主要版本。
  • 只使用第一方的造訪統計:不使用第三方 cookie,IP 位址在儲存前先經過雜湊,紀錄在 12 個月後清除。

決策與取捨

  • 選擇在伺服器端產生頁面,而不是在瀏覽器中載入:這樣 7 種語言的頁面都能被搜尋引擎索引,即使沒有 JavaScript 也能閱讀。
  • 選擇 systemd 服務,而不是 Docker:正式環境以沙箱隔離的 systemd 單元執行,並設有 CPU 與記憶體配額。一個從未實際部署的容器,什麼也證明不了。
  • 只提供一個公開的 JSON 資料來源(/api/contenus.json),由 Mindmap 重複利用,而不是再開發第二套 API:單一可信來源,不需要額外同步。

事件與修正

  • 網站改版後,pull request 的自動報告因為內嵌截圖,超過了 GitHub 留言 65,536 個字元的上限:測試全部通過,發布卻失敗。改用較輕量的縮圖,並把篇幅控制在 60,000 個字元以內後解決。
  • 部署後,Cloudflare 仍會持續提供舊版腳本長達 4 小時:現在每個 CSS 與 JavaScript 網址都帶有內容指紋(?v=),新版本就不會再和舊檔案混在一起。
  • 在阿拉伯文版本中,像「43 → 9.9 MB」這樣的數值,會被雙向文字演算法顯示成相反的方向:現在拉丁字母的數值都會以 Unicode 控制字元隔離。

仍待補強

  • 不自動重新開機(刻意的選擇)