# IONOS 共享主機（Web Hosting）部署指南 — 勞工與宿舍管理系統

> 適用：IONOS **Web Hosting / Virtual Server（共享主機）**，非 Cloud Server／VPS。  
> 共享主機**沒有 root**，因此**不能安裝寶塔面板**；本指南改用 IONOS 控制面板 + SFTP/SSH。  
> 需求：PHP 8.3+、MySQL、每分鐘 cron、可寫入 `storage/`。

---

## 〇、先看：共享主機會遇到什麼限制

| 項目                         | 狀況                        | 對應做法                                                                            |
| -------------------------- | ------------------------- | ------------------------------------------------------------------------------- |
| 寶塔面板                       | ❌ 需 root，裝不了              | 改用 IONOS 控制面板                                                                   |
| `queue:work` 常駐            | ❌ 無 Supervisor、SSH 程序會被终止 | 本系統 `routes/console.php:13` 已內建每分鐘消化 queue，靠 cron 即可                            |
| `php artisan storage:link` | 不需執行                      | 所有文件（PDF／Word／證照）都經 `response()->file()` 路由輸出，無實體 `/storage/` URL，避開 symlink 限制 |
| 網頁根目錄                      | ⚠️ 預設是 `httpdocs`         | 必須指向專案的 `public/`（見步驟四）                                                         |
| `DB_HOST`                  | ⚠️ **不是 `127.0.0.1`**     | 用面板给的 `dbXXXXXXXXX.hosting-data.io`                                             |
| 單次 PHP 執行時間                | ⚠️ 預設約 30 秒               | 5MB 月結 PDF、Excel 匯入會被中斷 → 用 `.user.ini` 放寬（步驟六）                                 |
| Cron 單次上限                  | ⚠️ IONOS 60 秒後強制終止        | 建議把 `--max-time=55` 降到 `45`（見十一、常見問題）                                           |
| SSH                        | ⚠️ 只有**主機主帳號**可用          | 次要 FTP 帳號無法跑 artisan                                                            |

---

## 一、事前準備（在 IONOS 控制面板確認）

登入 [central.ionos.com](https://central.ionos.com) → 左側【Hosting】：

1. **PHP 版本**：找到【PHP settings／Software】→ 版本選 **8.3 或 8.4**（本系統 `composer.json` 要求 `php: ^8.3`，8.1/8.2 不可）。
2. **SSH**：【SFTP & SSH】→ 啟動 SSH，並設定主帳號密碼。記下主機帳號（形如 `u1234567`）與伺服器位址。
3. **方案的 PHP CLI 名稱**：後方指令一律用 `php8.3-cli`（不是 `php`）。若版本為 8.4 就改寫 `php8.4-cli`。可用 SSH 內 `ls /usr/bin/php*` 查確認。

---

## 二、建立資料庫

【Hosting】→【Databases】→ 新增 MySQL 資料庫，記下面板顯示的四個值：

| 面板欄位              | 範例                          | 填到 `.env` 的              |
| ----------------- | --------------------------- | ------------------------ |
| Database name     | `u1234567_worker_system`    | `DB_DATABASE`            |
| Username          | `u1234567_admin`            | `DB_USERNAME`            |
| Password          | 自訂（有強度要求）                   | `DB_PASSWORD`            |
| **Server / Host** | `db1234567.hosting-data.io` | `DB_HOST` ← **最容易填錯的一格** |
| Port              | `3306`                      | `DB_PORT`                |

> ⚠️ 資料庫名稱與帳號會被 IONOS 加上 `u1234567_` 前綴，請以面板顯示的**完整字串**為準，不要自己加。

---

## 三、上傳專案

### 1. 打包（在本機 Windows 執行）

部署包需含 `vendor/`（共享主機建 Composer 較麻煩，直接附帶最快）。要排除的只有：`.git`、`node_modules`、`.env`、`tests`、本機暫存檔與 `storage` 內的舊日誌。

### 2. 上傳位置

用 SFTP（WinSCP / FileZilla，協定選 **SFTP**，用主帳號）連到 IONOS，把專案放在**與 `httpdocs` 同層**的資料夾，例如：

```
/home/u1234567/worker-system        ← 專案（含 artisan、vendor/、.env）
/home/u1234567/httpdocs             ← IONOS 預設網頁根目錄
```

放在 `httpdocs` **外面**是刻意的：`.env`（含資料庫密碼）就不在網頁可達範圍內。

在 SSH 內解壓：

```bash
cd ~
tar -xzf worker-system-deploy.tar.gz      # 或 unzip
mv worker-system ~/worker-system 2>/dev/null || true
```

---

## 四、把網站目錄指向 `public/`（安全關鍵步驟）

【Hosting】→【Websites / Domains & SSL】→ 選域名 → 進階設定裡把**網站目錄／routing 資料夾**改為：

```
worker-system/public
```

儲存後，`https://你的域名/` 應該直接導向 Laravel 登入頁。

### 若你的方案找不到「網站目錄」設定（備援做法）

專案改放 `~/httpdocs/worker-system`，並在 `~/httpdocs/.htaccess` 寫入轉發：

```apache
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^(.*)$ worker-system/public/$1 [L]
</IfModule>
```

並在 `~/httpdocs/worker-system/.htaccess` 加一道防線（避免有人直接摸 `.env`）：

```apache
<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
```

> 這條 `.htaccess` 放在**專案根**（`worker-system/`），不是 `public/` 內；`public/` 自有 Laravel 的 `.htaccess`，兩者不要搞混。  
> 用備援做法後，**一定要做步驟八的 `.env` 洩漏測試**。

---

## 五、設定 `.env` 並初始化

```bash
cd ~/worker-system
cp .env.example .env
php8.3-cli artisan key:generate
```

編輯 `.env`（用 SFTP 直接改，或 `nano .env`）：

```
APP_NAME="申請外勞管理系統"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://你的域名

APP_LOCALE=zh_HK
APP_FALLBACK_LOCALE=zh_HK

DB_CONNECTION=mysql
DB_HOST=db1234567.hosting-data.io
DB_PORT=3306
DB_DATABASE=u1234567_worker_system
DB_USERNAME=u1234567_admin
DB_PASSWORD=你的密碼

SESSION_DRIVER=database
QUEUE_CONNECTION=database
CACHE_STORE=database
FILESYSTEM_DISK=local

LOG_LEVEL=warning
```

> - `APP_URL` 請用 `https://`（IONOS 免費 SSL 開好後再定案）。
> - `SESSION_DRIVER`／`CACHE_STORE` 必須留 `database`：排程的 `withoutOverlapping()` 鎖靠資料庫快取，共享主機沒有 Redis。
> - `QUEUE_CONNECTION=database` 保持不變 — 不要改成 `sync`，否則上傳 PDF／Excel 匯入會在單一請求裡跑完，容易撞 30 秒上限。

接著初始化（**不需要** `storage:link`）：

```bash
php8.3-cli artisan migrate --force
php8.3-cli artisan config:cache
php8.3-cli artisan route:cache
php8.3-cli artisan view:cache

# 建立管理員（會輸出自動產生的密碼，記下）
php8.3-cli artisan app:provision-admin
```

權限（共享主機通常已是同用戶執行，若報 500 再執行）：

```bash
chmod -R 775 storage bootstrap/cache
```

---

## 六、放寬 PHP 限制（PDF／Excel 功能必要）

在**網頁根目錄**（即 `~/worker-system/public/`）建立 `.user.ini`：

```ini
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
upload_max_filesize = 64M
post_max_size = 64M
```

> IONOS 有部分 directive 由面板控制、`.user.ini` 蓋不下去。若在【Hosting → PHP settings】能看到對應項目，優先在面板改；改完等 5～10 分鐘快取生效。  
> 驗證：臨時在 SSH 跑 `php8.3-cli -i | grep -E "memory_limit|max_execution_time"`，或看 500 日誌。

---

## 七、排程與 Queue（一定要做，不然距離更新與到期提醒不會動）

【Hosting】→【Cronjobs】→【Manage】→ 新增：

| 欄位 | 內容                                                                               |
| -- | -------------------------------------------------------------------------------- |
| 類型 | **Command / 命令列**（不是 URL）                                                        |
| 頻率 | 每 1 分鐘（`* * * * *`）                                                              |
| 命令 | `php8.3-cli /home/u1234567/worker-system/artisan schedule:run >> /dev/null 2>&1` |

這一個 cron 同時覆蓋兩件事（`routes/console.php` 已定義）：

- 每分鐘 `queue:work --stop-when-empty --max-time=55` → 智能分配的距離／地理編碼更新
- 每天 08:00 `notifications:send-expiry-reminders` → 合約與文件到期提醒

> ⚠️ IONOS cron 單次執行上限約 **60 秒**。現行 `--max-time=55` 加計 `schedule:run` 自身開銷非常接近上限，被強制terminated 時 queue 任務可能卡在 `reserved` 狀態。建議改為 `45`（見下一節）。

---

## 八、部署後驗證清單

- [ ] 開首頁 → 導向登入頁（不是 404、不是空白）
- [ ] 管理員登入成功、儀表板數字正常
- [ ] **安全檢查**：瀏覽器直接開 `https://你的域名/.env` → 必須 **404**
- [ ] **安全檢查**：`https://你的域名/vendor/autoload.php` → 必須 404
- [ ] 【Demo 資料】→「生成 Demo 資料」→ 試用帳號 `demo_iris`／`demo_kent`（密碼 `demo12345`）可登入
- [ ] 工人詳情頁 PDF 可預覽
- [ ] 會計 → 月結報告 → 下載 PDF，中文正常、無方框（檔約 5MB；若逾時見第六節）
- [ ] 工人詳情 →「立即匯出」Word 正常
- [ ] 智能分配 →「更新距離資料」→ 1～2 分鐘後重新整理看到距離（驗證 cron 有在跑）
- [ ] 側欄通知鈴铛有計數（验证 `schedule:run` 有執行）

---

## 九、每日備份（共享主機自備）

IONOS 有備份方案，但資料庫仍建議自己 dump。於 SSH：

```bash
mysqldump -h db1234567.hosting-data.io -u u1234567_admin -p \
  --single-transaction u1234567_worker_system > ~/backup_db_$(date +%F).sql
```

再搭配 cron 每週執行，並用 SFTP 下載到本機（`storage/app/private/documents/` 內是客户實際文件，也要一併備份）。

---

## 十、上線後日常更新流程

共享主機沒有 Git pull + deploy hook 的乾淨做法，建議沿用在測試機已用的「差異包 + 覆蓋」模式：

```bash
cd ~/worker-system
php8.3-cli artisan down
# 解壓新差異包覆蓋 app/ resources/ routes/ database/ ...
php8.3-cli artisan migrate --force
php8.3-cli artisan optimize:clear
php8.3-cli artisan config:cache && php8.3-cli artisan route:cache && php8.3-cli artisan view:cache
php8.3-cli artisan up
```

---

## 十一、常見問題

| 症狀                                                          | 原因／解法                                                                                        |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| 500 錯誤                                                      | `tail -200 ~/worker-system/storage/logs/laravel.log`；多為 `.env` 未設好或 `DB_HOST` 填了 `127.0.0.1` |
| 白畫面、樣式全亂                                                    | 網站目錄沒指向 `public/`（步驟四）                                                                       |
| 「PHP version too low」／`symfony/console requires PHP >= 8.x` | 面板的 PHP 版本未改 8.3+；注意**網頁**與 **CLI** 是兩處設定                                                    |
| `php artisan` 找不到指令                                         | 用了 `php` 而非 `php8.3-cli`；或用了次要 FTP 帳號登入（無 SSH）                                               |
| PDF 只有方框／亂碼                                                 | 確認 `public/fonts/NotoSansTC-Regular.ttf` 有上傳；清 `storage/fonts/*` 重試                          |
| 月結 PDF／Excel 匯入失敗在一半                                        | PHP 執行時間或 `memory_limit` 不足（步驟六）；或超过 IONOS 60 秒 cron 上限                                      |
| 距離更新沒反應                                                     | Cronjob 未建立／未啟用，或 cron 類型選成 URL                                                              |
| queue 任務一直重試不完成                                             | 每分鐘被 60 秒上限切斷 → 把 `--max-time=55` 改為 `45`，並清掉卡住的 job：`php8.3-cli artisan queue:retry all`    |
| 419 PAGE EXPIRED（匯入表單）                                      | 請求被 `post_max_size` 擋掉，先做步驟六                                                                 |
| 忘記管理員密碼                                                     | `php8.3-cli artisan app:provision-admin --password=新密碼`                                      |
| `.env` 可被下載                                                 | 網站目錄設定錯了 — 立刻改為 `worker-system/public`，並重設資料庫密碼                                              |

---

## 十二、何时该改用 VPS

若發生以下任一情況，共享主機就不合適，請改買 IONOS Cloud Server（VPS）亚沿用 `BT-DEPLOY.md`：

- 客户實際資料量大、月結 PDF 與 Excel 批次匯入頻繁逾時
- 需要常駐 `queue:work`（多工時距離更新更即時）
- 需要 Composer、Redis、Supervisor、自訂 Nginx 設定
- `.user.ini` 被 IONOS 鎖住、無法放寬 PHP 限制
