# ROLAC — Infrastructure Design

**平台:** Microsoft Azure (Nonprofit $2,000 Credit)  
**文件版本:** v0.1 (2026-05-24)

---

## 目錄

1. [Azure 資源規劃](#1-azure-資源規劃)
2. [VM 容器架構](#2-vm-容器架構)
3. [Docker Compose 服務清單](#3-docker-compose-服務清單)
4. [網路與 DNS](#4-網路與-dns)
5. [Nginx 反向代理路由](#5-nginx-反向代理路由)
6. [CI/CD Pipeline (Jenkins + Gitea)](#6-cicd-pipeline-jenkins--gitea)
7. [Azure Storage Account 使用規劃](#7-azure-storage-account-使用規劃)
8. [備份策略](#8-備份策略)
9. [成本估算 (Monthly)](#9-成本估算-monthly)
10. [部署步驟清單](#10-部署步驟清單)

---

## 1. Azure 資源規劃

| 資源 | 規格 | 用途 | 估計月費 |
|------|------|------|----------|
| **Azure VM** | Standard B2s (2 vCPU / 4 GB RAM) | 主機：所有 Docker 容器 | ~$30–35/月 |
| **Azure Storage Account** | LRS, Cool tier | Blob 儲存（照片/PDF/媒體） | ~$2–5/月 |
| **Azure Static Public IP** | Standard | 固定對外 IP | ~$4/月 |
| **Azure DNS Zone** *(可選)* | — | 管理 DNS 記錄 | ~$0.5/月 |
| **OS Disk** | Premium SSD 64 GB | VM 系統碟 | ~$10/月 |
| **資料磁碟** | Standard SSD 128 GB | Docker volumes（DB、Gitea、Jenkins） | ~$11/月 |

> **總估算: ~$57–65/月**，年約 $700，在 $2,000 Credit 範圍內可運行約 **30 個月**。  
> 若流量低，可降級至 **B1ms (1 vCPU / 2 GB)** 節省至 ~$15/月。

---

## 2. VM 容器架構

```
Azure VM (Ubuntu 22.04 LTS)
│
├── /data/                        ← 資料磁碟掛載點
│   ├── postgres/                 ← PostgreSQL data
│   ├── gitea/                    ← Gitea repos & config
│   ├── jenkins/                  ← Jenkins home
│   └── nginx/certs/              ← SSL 憑證
│
└── Docker Engine
    │
    ├── [nginx]          :80, :443   ← 反向代理 + SSL 終止
    ├── [angular-app]    :4200       ← Angular SPA (nginx serve)
    ├── [rolac-api]      :5000       ← ASP.NET Core API
    ├── [postgres]       :5432       ← PostgreSQL 14+
    ├── [gitea]          :3000, :22  ← 原始碼管理
    └── [jenkins]        :8080       ← CI/CD
```

---

## 3. Docker Compose 服務清單

```yaml
# docker-compose.yml (簡化草圖)

version: '3.9'

services:

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/certs:/etc/letsencrypt
    depends_on:
      - angular-app
      - rolac-api

  angular-app:
    image: rolac/frontend:latest       # 由 Jenkins build
    expose:
      - "80"
    restart: unless-stopped

  rolac-api:
    image: rolac/api:latest            # 由 Jenkins build
    expose:
      - "5000"
    environment:
      - ConnectionStrings__Default=Host=postgres;...
      - Azure__StorageAccount__ConnectionString=...
      - Jwt__Secret=${JWT_SECRET}
    depends_on:
      - postgres
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    volumes:
      - /data/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=rolac
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    expose:
      - "5432"
    restart: unless-stopped

  gitea:
    image: gitea/gitea:latest
    ports:
      - "2222:22"                      # Git SSH
    expose:
      - "3000"                         # Web UI (透過 nginx 代理)
    volumes:
      - /data/gitea:/data
    restart: unless-stopped

  jenkins:
    image: jenkins/jenkins:lts-jdk21
    expose:
      - "8080"                         # Web UI (透過 nginx 代理)
    volumes:
      - /data/jenkins:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock   # Jenkins 可操作 Docker
    restart: unless-stopped
```

> **Secrets 管理:** 使用 `.env` 檔搭配 `docker-compose --env-file`，**不得** commit 到 Gitea。

---

## 4. 網路與 DNS

```
Internet
   │
   ▼
Azure Public IP (Static)
   │
   ▼
Azure NSG (Network Security Group)
   ├── Port 80  (HTTP → 轉 443)
   ├── Port 443 (HTTPS)
   └── Port 2222 (Git SSH)
   │
   ▼
Ubuntu VM → Docker → Nginx
```

**DNS 記錄 — rolac.org**

| 子域名 | 完整網址 | 類型 | 指向 | 用途 |
|--------|----------|------|------|------|
| `@` | rolac.org | A | VM Public IP | 教會公開網站（根域名） |
| `www` | www.rolac.org | CNAME | rolac.org | 同上（重定向） |
| `app` | app.rolac.org | A | VM Public IP | Angular 後台管理入口 |
| `api` | api.rolac.org | A | VM Public IP | ASP.NET Core REST API |
| `git` | git.rolac.org | A | VM Public IP | Gitea 原始碼管理 |
| `ci` | ci.rolac.org | A | VM Public IP | Jenkins CI/CD |

---

## 5. Nginx 反向代理路由

```nginx
# /nginx/conf.d/rolac.conf (草圖)

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name *.rolac.org rolac.org;
    return 301 https://$host$request_uri;
}

# 教會公開網站 (Angular SSR or static)
server {
    listen 443 ssl;
    server_name rolac.org www.rolac.org;
    location / { proxy_pass http://angular-app:80; }
}

# 後台管理 App
server {
    listen 443 ssl;
    server_name app.rolac.org;
    location / { proxy_pass http://angular-app:80; }
}

# API
server {
    listen 443 ssl;
    server_name api.rolac.org;
    location / {
        proxy_pass http://rolac-api:5000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Gitea
server {
    listen 443 ssl;
    server_name git.rolac.org;
    location / { proxy_pass http://gitea:3000; }
}

# Jenkins
server {
    listen 443 ssl;
    server_name ci.rolac.org;
    location / { proxy_pass http://jenkins:8080; }
}
```

**SSL 憑證 (Let's Encrypt)**
```bash
certbot certonly --webroot -w /data/nginx/www \
  -d rolac.org \
  -d www.rolac.org \
  -d app.rolac.org \
  -d api.rolac.org \
  -d git.rolac.org \
  -d ci.rolac.org
```

---

## 6. CI/CD Pipeline (Jenkins + Gitea)

### 流程圖

```
開發者 push → Gitea
                │
                │ Webhook (HTTP POST)
                ▼
            Jenkins Job
                │
    ┌───────────┼───────────┐
    ▼           ▼           ▼
  Build       Test        Lint
  Docker     dotnet      ng lint
  image      test
    │
    ▼
  Push image (Web)       ← rolac.org & app.rolac.org 用
  docker compose up -d
    │
    ▼ (main branch only)
  Capacitor Build        ← 產出 iOS .ipa / Android .apk
  npx cap build ios
  npx cap build android
    │
    ▼
  Health check
  通知結果 (Email)
```

> **注意：** Capacitor build (iOS) 需要 macOS 環境，Jenkins 跑在 Linux VM 上只能產 Android APK；iOS `.ipa` 需在 Mac 上另外 build 或使用 Xcode Cloud。

### Jenkinsfile 草圖

```groovy
pipeline {
    agent any

    environment {
        API_IMAGE  = "rolac/api:${BUILD_NUMBER}"
        WEB_IMAGE  = "rolac/frontend:${BUILD_NUMBER}"
    }

    stages {
        stage('Checkout') {
            steps { checkout scm }
        }

        stage('Test API') {
            steps {
                sh 'dotnet test ./backend/ROLAC.Tests'
            }
        }

        stage('Build Images') {
            parallel {
                stage('API') {
                    steps {
                        sh 'docker build -t ${API_IMAGE} -t rolac/api:latest ./backend'
                    }
                }
                stage('Frontend (Web)') {
                    steps {
                        sh 'docker build -t ${WEB_IMAGE} -t rolac/frontend:latest ./frontend'
                    }
                }
            }
        }

        stage('Deploy Web') {
            when { branch 'main' }
            steps {
                sh 'docker compose -f /opt/rolac/docker-compose.yml up -d --no-deps rolac-api angular-app'
            }
        }

        stage('Build Android APK') {
            when { branch 'main' }
            steps {
                dir('frontend') {
                    sh 'npm ci'
                    sh 'npm run build -- --configuration production'
                    sh 'npx cap sync android'
                    sh 'cd android && ./gradlew assembleRelease'
                }
                archiveArtifacts artifacts: 'frontend/android/app/build/outputs/apk/release/*.apk'
            }
        }
    }

    post {
        failure { mail to: 'admin@rolac.org', subject: "Build FAILED: ${env.JOB_NAME} #${BUILD_NUMBER}" }
        success { mail to: 'admin@rolac.org', subject: "Build OK: ${env.JOB_NAME} #${BUILD_NUMBER}" }
    }
}
```

### Branch 策略

| Branch | 說明 | 部署 |
|--------|------|------|
| `main` | 正式版本 | 自動部署 Production |
| `develop` | 開發整合 | 自動部署 Staging (同 VM，不同 port) |
| `feature/*` | 功能開發 | 僅跑 Build + Test，不部署 |
| `hotfix/*` | 緊急修復 | 審核後 merge 至 main |

---

## 7. Azure Storage Account 使用規劃

### Container (Blob) 結構

```
rolac-storage (Storage Account)
├── members/
│   ├── photos/          ← 教友大頭照
│   └── documents/       ← 個人相關文件
├── receipts/
│   └── {year}/          ← 年度奉獻收據 PDF
├── cms/
│   ├── images/          ← 網站上傳圖片
│   └── attachments/     ← 公告附件
└── reports/
    └── exports/         ← 匯出的報表 PDF/CSV
```

### 存取控制

| Container | 存取層級 | 說明 |
|-----------|----------|------|
| `members/` | **Private** | 僅 API 透過 SAS Token 存取 |
| `receipts/` | **Private** | API 產生限時下載連結給教友 |
| `cms/images/` | **Blob (Public Read)** | 網站圖片可公開存取 |
| `reports/` | **Private** | 僅管理員 API 存取 |

### C# 整合

```csharp
// 使用 Azure.Storage.Blobs SDK
// 上傳教友照片範例
var blobClient = new BlobContainerClient(connectionString, "members");
await blobClient.UploadBlobAsync($"photos/{memberId}.jpg", fileStream);

// 產生限時 SAS URL (收據下載)
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Read,
    DateTimeOffset.UtcNow.AddHours(1));
```

---

## 8. 備份策略

### PostgreSQL 備份

```bash
# crontab (每日 02:00 UTC 備份)
0 2 * * * docker exec postgres pg_dump -U rolac_user rolac \
  | gzip > /data/backups/rolac_$(date +%Y%m%d).sql.gz

# 保留 30 天
find /data/backups -name "*.sql.gz" -mtime +30 -delete
```

### 備份上傳至 Azure Blob

```bash
# 使用 azcopy 或 Azure CLI 將備份上傳
azcopy copy "/data/backups/rolac_$(date +%Y%m%d).sql.gz" \
  "https://rolacstorage.blob.core.windows.net/backups/"
```

### 備份範圍

| 資料 | 方式 | 頻率 | 保留 |
|------|------|------|------|
| PostgreSQL DB | pg_dump → Azure Blob | 每日 | 30 天 |
| Gitea repos | volume backup → Azure Blob | 每日 | 14 天 |
| Jenkins config | volume backup | 每週 | 4 週 |
| Azure Blob 本身 | Azure Soft Delete | 自動 | 7 天 |

---

## 9. 成本估算 (Monthly)

| 項目 | 規格 | 月費 (USD) |
|------|------|-----------|
| Azure VM | Standard_B2s | $30 |
| OS Disk | Premium SSD 64 GB | $10 |
| 資料磁碟 | Standard SSD 128 GB | $11 |
| Static Public IP | Standard | $4 |
| Storage Account | LRS 50 GB | $2 |
| 出站流量 | ~10 GB/月 | $1 |
| **合計** | | **~$58/月** |

> 💡 **$2,000 nonprofit credit → 約 34 個月 (接近 3 年)**  
> 如規模成長，可升級 VM 規格但仍在 credit 範圍內。

---

## 10. 部署步驟清單

### Phase 0 — Azure 環境建置

- [ ] 申請 Microsoft Nonprofit Azure 帳號
- [ ] 建立 Resource Group: `rg-rolac-prod`
- [ ] 建立 Azure VM (Ubuntu 22.04, Standard_B2s)
- [ ] 建立資料磁碟 (128 GB) 並掛載至 `/data`
- [ ] 設定 NSG (開放 80, 443, 2222)
- [ ] 建立 Static Public IP 並綁定
- [ ] 建立 Storage Account (`rolacstorage`)
- [ ] 設定 DNS A 記錄指向 VM IP

### Phase 0 — VM 環境初始化

- [ ] 安裝 Docker Engine + Docker Compose
- [ ] 安裝 Certbot (Let's Encrypt)
- [ ] 建立目錄結構 (`/data/postgres`, `/data/gitea`, `/data/jenkins`)
- [ ] 啟動 Gitea 容器，建立組織與 Repo
- [ ] 啟動 Jenkins 容器，安裝 Plugins (Git, Docker, Pipeline)
- [ ] 設定 Gitea Webhook → Jenkins
- [ ] 申請 SSL 憑證 (certbot)
- [ ] 啟動 Nginx 容器，配置反向代理

### Phase 1+ — 每次 Release

- [ ] Push code to Gitea `main` branch
- [ ] Jenkins 自動觸發 Build → Test → Deploy
- [ ] 確認 Health Check endpoint 回應正常
- [ ] 必要時執行 EF Core Migration: `dotnet ef database update`
