ソースを参照

feat(deploy): replace nginx with caddy for automatic HTTPS

- Replace nginx with caddy for automatic HTTPS (Let's Encrypt)
- Configure Caddy with basic auth using bcrypt password hash
- Update docker-compose.yml with caddy service and volumes
- Update API_AUTH.md with caddy-specific instructions
- Update .env.example with basic auth variables
- Delete old nginx.conf

Domain: love.hdlife.me
lushdog@outlook.com 1 ヶ月 前
コミット
ac939b20fa
5 ファイル変更136 行追加64 行削除
  1. 12 5
      .env.example
  2. 81 37
      API_AUTH.md
  3. 28 0
      Caddyfile
  4. 15 6
      docker-compose.yml
  5. 0 16
      nginx.conf

+ 12 - 5
.env.example

@@ -1,7 +1,14 @@
+# Solana 配置
+# 请复制此文件为 .env(生产环境)或 .env.local(本地开发)并填入真实值
+# 注意:包含敏感信息的 .env 文件不会被提交到 Git
+
 # Solana RPC 地址
-# 请复制此文件为 .env.local 并填入真实的 RPC 地址
-# 注意:.env.local 文件不会被提交到 Git
-SOL_ENDPOINT=
+SOL_ENDPOINT=https://lb.drpc.live/solana/YOUR_API_KEY
+
 # Solana 私钥(用于签名交易,敏感信息,不要提交到 Git)
-# 使用 docker-compose 时,可以创建 .env 文件(与 docker-compose.yml 同级)
-SOL_SECRET_KEY=
+SOL_SECRET_KEY=your_base58_encoded_secret_key
+
+# Basic Auth 配置(用于 HTTPS 访问保护)
+# 生成密码哈希命令:docker run --rm caddy:2-alpine caddy hash-password --plaintext 'your_password'
+BASIC_AUTH_USER=admin
+BASIC_AUTH_HASH=$2a$14$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

+ 81 - 37
API_AUTH.md

@@ -1,4 +1,4 @@
-# API Basic Auth 认证指南
+# API Basic Auth 认证指南(Caddy 版本)
 
 本文档说明外部服务器如何安全地调用本项目的 API 接口(已配置 Basic Auth)。
 
@@ -14,27 +14,32 @@
 
 ---
 
-## 配置方式
+## 配置 Basic Auth
 
-### 1. 设置环境变量
+### 1. 生成密码哈希
 
-在外部服务器的环境中设置以下变量
+Caddy 使用 bcrypt 哈希格式存储密码。使用以下命令生成
 
 ```bash
-# Basic Auth 用户名
-export BYREAL_API_USERNAME=admin
+# 使用 Docker 生成密码哈希(推荐)
+docker run --rm caddy:2-alpine caddy hash-password --plaintext 'your_password'
 
-# Basic Auth 密码
-export BYREAL_API_PASSWORD=your_password_here
+# 示例输出:
+# $2a$14$Z3Q7g2n8XQYuH9vJkLmNqOrStUvWxYzAbCdEfGhIjKlMnOpQrStUv
 ```
 
-或者在 `.env` 文件中(不要提交到 Git):
+### 2. 配置环境变量
+
+在服务器上的 `.env` 文件中添加:
 
 ```bash
-BYREAL_API_USERNAME=admin
-BYREAL_API_PASSWORD=your_password_here
+# Basic Auth 配置
+BASIC_AUTH_USER=admin
+BASIC_AUTH_HASH=$2a$14$Z3Q7g2n8XQYuH9vJkLmNqOrStUvWxYzAbCdEfGhIjKlMnOpQrStUv
 ```
 
+**注意**:将 `BASIC_AUTH_HASH` 替换为你实际生成的哈希值。
+
 ---
 
 ## 代码示例
@@ -52,8 +57,8 @@ const password = process.env.BYREAL_API_PASSWORD || ''
 const credentials = Buffer.from(`${username}:${password}`).toString('base64')
 const authHeader = `Basic ${credentials}`
 
-// 发送请求
-const response = await fetch('http://your-server-ip/api/my-lp', {
+// 发送请求(使用 HTTPS)
+const response = await fetch('https://love.hdlife.me/api/my-lp', {
 	method: 'GET',
 	headers: {
 		Authorization: authHeader,
@@ -72,7 +77,7 @@ import axios from 'axios'
 const username = process.env.BYREAL_API_USERNAME || ''
 const password = process.env.BYREAL_API_PASSWORD || ''
 
-const response = await axios.get('http://your-server-ip/api/my-lp', {
+const response = await axios.get('https://love.hdlife.me/api/my-lp', {
 	auth: {
 		username,
 		password,
@@ -85,7 +90,7 @@ const response = await axios.get('http://your-server-ip/api/my-lp', {
 const data = response.data
 ```
 
-#### 使用 ky(类似你项目中的方式)
+#### 使用 ky
 
 ```typescript
 import ky from 'ky'
@@ -95,7 +100,7 @@ const password = process.env.BYREAL_API_PASSWORD || ''
 
 const credentials = Buffer.from(`${username}:${password}`).toString('base64')
 
-const response = await ky.get('http://your-server-ip/api/my-lp', {
+const response = await ky.get('https://love.hdlife.me/api/my-lp', {
 	headers: {
 		Authorization: `Basic ${credentials}`,
 	},
@@ -121,7 +126,7 @@ password = os.getenv('BYREAL_API_PASSWORD', '')
 
 # 方式 1: 使用 HTTPBasicAuth(推荐)
 response = requests.get(
-    'http://your-server-ip/api/my-lp',
+    'https://love.hdlife.me/api/my-lp',
     auth=HTTPBasicAuth(username, password),
     headers={'Content-Type': 'application/json'}
 )
@@ -135,7 +140,7 @@ headers = {
     'Authorization': f'Basic {credentials}',
     'Content-Type': 'application/json'
 }
-response = requests.get('http://your-server-ip/api/my-lp', headers=headers)
+response = requests.get('https://love.hdlife.me/api/my-lp', headers=headers)
 ```
 
 ---
@@ -145,11 +150,11 @@ response = requests.get('http://your-server-ip/api/my-lp', headers=headers)
 ```bash
 # 从环境变量读取
 curl -u "${BYREAL_API_USERNAME}:${BYREAL_API_PASSWORD}" \
-  http://your-server-ip/api/my-lp
+  https://love.hdlife.me/api/my-lp
 
 # 或者直接指定(不推荐,密码会出现在命令历史中)
 curl -u "admin:your_password" \
-  http://your-server-ip/api/my-lp
+  https://love.hdlife.me/api/my-lp
 ```
 
 ---
@@ -173,7 +178,7 @@ func main() {
 
     credentials := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
 
-    req, _ := http.NewRequest("GET", "http://your-server-ip/api/my-lp", nil)
+    req, _ := http.NewRequest("GET", "https://love.hdlife.me/api/my-lp", nil)
     req.Header.Set("Authorization", "Basic "+credentials)
     req.Header.Set("Content-Type", "application/json")
 
@@ -211,7 +216,7 @@ public class ApiClient {
 
         HttpClient client = HttpClient.newHttpClient();
         HttpRequest request = HttpRequest.newBuilder()
-            .uri(URI.create("http://your-server-ip/api/my-lp"))
+            .uri(URI.create("https://love.hdlife.me/api/my-lp"))
             .header("Authorization", "Basic " + credentials)
             .header("Content-Type", "application/json")
             .GET()
@@ -248,29 +253,58 @@ services:
 
 ---
 
+## 部署步骤
+
+### 1. 配置环境变量
+
+在服务器上创建 `.env` 文件:
+
+```bash
+# Solana 配置
+SOL_ENDPOINT=your_solana_endpoint
+SOL_SECRET_KEY=your_secret_key
+
+# Basic Auth 配置
+BASIC_AUTH_USER=admin
+BASIC_AUTH_HASH=$2a$14$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+```
+
+### 2. 启动服务
+
+```bash
+docker compose down
+docker compose up -d
+```
+
+### 3. 验证 HTTPS
+
+访问 `https://love.hdlife.me`,应该会自动跳转到 HTTPS 并要求 Basic Auth。
+
+---
+
 ## 安全最佳实践
 
 1. ✅ **使用环境变量**:永远不要在代码中硬编码密码
 2. ✅ **使用 `.gitignore`**:确保包含密码的 `.env` 文件不会被提交
 3. ✅ **使用密钥管理服务**:生产环境推荐使用 AWS Secrets Manager、Azure Key Vault 等
 4. ✅ **定期轮换密码**:定期更换 Basic Auth 密码
-5. ✅ **使用 HTTPS**:生产环境必须使用 HTTPS(当前是 HTTP,仅用于开发/内网)
-6. ✅ **限制访问 IP**:在 Nginx 中配置 `allow/deny` 限制允许访问的 IP
+5. ✅ **使用 HTTPS**:Caddy 自动配置 HTTPS,无需额外操作
+6. ✅ **限制访问 IP**:在服务器防火墙中限制允许的 IP
 7. ✅ **最小权限原则**:只为需要的服务提供凭证
 
 ---
 
 ## 测试连接
 
-使用 curl 测试 Basic Auth 是否配置正确:
+使用 curl 测试 Basic Auth 和 HTTPS
 
 ```bash
 # 测试(会提示输入密码)
-curl -u admin http://your-server-ip/api/my-lp
+curl -u admin https://love.hdlife.me/api/my-lp
 
 # 或者从环境变量读取
 curl -u "${BYREAL_API_USERNAME}:${BYREAL_API_PASSWORD}" \
-  http://your-server-ip/api/my-lp
+  https://love.hdlife.me/api/my-lp
 ```
 
 如果返回 401 Unauthorized,说明凭证错误;如果返回 200 或其他业务状态码,说明认证成功。
@@ -281,31 +315,41 @@ curl -u "${BYREAL_API_USERNAME}:${BYREAL_API_PASSWORD}" \
 
 ### Q: 如何获取 Basic Auth 凭证?
 
-A: 联系项目管理员获取用户名和密码。密码存储在服务器的 `htpasswd` 文件中。
+A: 联系项目管理员获取用户名和密码。密码哈希存储在服务器的 `.env` 文件中。
 
 ### Q: 密码忘记了怎么办?
 
-A: 在服务器上重新生成:
+A: 在服务器上重新生成哈希并更新 `.env` 文件
 
 ```bash
-htpasswd ./htpasswd admin
-# 输入新密码
+# 生成新密码哈希
+docker run --rm caddy:2-alpine caddy hash-password --plaintext 'new_password'
+
+# 更新 .env 文件中的 BASIC_AUTH_HASH
+# 然后重启服务
+docker compose restart caddy
 ```
 
-然后重启 Docker 容器:
+### Q: 如何查看 Caddy 日志?
+
+A:
 
 ```bash
-docker compose restart nginx
+# 查看 Caddy 容器日志
+docker logs byreal-caddy
+
+# 查看访问日志(如果在 Caddyfile 中配置了日志文件)
+docker exec byreal-caddy cat /var/log/caddy/access.log
 ```
 
-### Q: 如何在生产环境使用 HTTPS?
+### Q: HTTPS 证书如何续期
 
-A: 需要配置 SSL 证书,修改 `nginx.conf` 添加 SSL 配置,并更新 `docker-compose.yml` 映射 443 端口
+A: Caddy 会自动管理 Let's Encrypt 证书的续期,无需手动操作
 
 ---
 
 ## 相关文件
 
-- `nginx.conf` - Nginx Basic Auth 配置
-- `htpasswd` - Basic Auth 密码文件(不提交到 Git)
+- `Caddyfile` - Caddy HTTPS + Basic Auth 配置
 - `docker-compose.yml` - Docker 服务配置
+- `.env` - 环境变量配置(不提交到 Git)

+ 28 - 0
Caddyfile

@@ -0,0 +1,28 @@
+love.hdlife.me {
+	# Basic Auth - 使用环境变量中的哈希密码
+	basicauth {
+		{env.BASIC_AUTH_USER} {env.BASIC_AUTH_HASH}
+	}
+
+	# 反向代理到 byreal-table 服务
+	reverse_proxy byreal-table:3000
+
+	# 日志配置
+	log {
+		output file /var/log/caddy/access.log {
+			roll_size 10MB
+			roll_keep 5
+		}
+	}
+
+	# 安全响应头
+	header {
+		# 移除 Server 头
+		-Server
+		# 安全头
+		Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+		X-Content-Type-Options "nosniff"
+		X-Frame-Options "DENY"
+		X-XSS-Protection "1; mode=block"
+	}
+}

+ 15 - 6
docker-compose.yml

@@ -6,7 +6,6 @@ services:
       args:
         SOL_ENDPOINT: ${SOL_ENDPOINT:-}
     container_name: byreal-table
-    # 不要再映射 80:3000,改成内部用的 3000 端口(也可以直接去掉 ports,用 expose)
     expose:
       - '3000'
     environment:
@@ -18,19 +17,29 @@ services:
     networks:
       - byreal-network
 
-  nginx:
-    image: nginx:alpine
-    container_name: byreal-nginx
+  caddy:
+    image: caddy:2-alpine
+    container_name: byreal-caddy
     depends_on:
       - byreal-table
     ports:
       - '80:80'
+      - '443:443'
     volumes:
-      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
-      - ./htpasswd:/etc/nginx/.htpasswd:ro
+      - ./Caddyfile:/etc/caddy/Caddyfile:ro
+      - caddy_data:/data
+      - caddy_config:/config
+    environment:
+      - BASIC_AUTH_USER=${BASIC_AUTH_USER:-admin}
+      - BASIC_AUTH_HASH=${BASIC_AUTH_HASH}
     networks:
       - byreal-network
+    restart: unless-stopped
 
 networks:
   byreal-network:
     driver: bridge
+
+volumes:
+  caddy_data:
+  caddy_config:

+ 0 - 16
nginx.conf

@@ -1,16 +0,0 @@
-server {
-  listen 80;
-  server_name _;
-
-  # 开启 Basic Auth
-  auth_basic "Restricted";
-  auth_basic_user_file /etc/nginx/.htpasswd;
-
-  location / {
-    proxy_pass http://byreal-table:3000;
-    proxy_set_header Host $host;
-    proxy_set_header X-Real-IP $remote_addr;
-    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-    proxy_set_header X-Forwarded-Proto $scheme;
-  }
-}