Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.build-cache
dist
docker-data
logs
*.log
.DS_Store
claude-server
data.sqlite3
展示图
38 changes: 38 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1.7

FROM golang:1.24-alpine AS builder

WORKDIR /src

ARG GOPROXY=https://proxy.golang.org,direct
ENV GOPROXY=${GOPROXY}

COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
sh -c 'for i in 1 2 3; do go mod download && exit 0; echo "go mod download failed, retrying ($i/3)..." >&2; sleep 5; done; exit 1'

COPY . .

RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/claude-server .

FROM alpine:3.20

RUN apk add --no-cache ca-certificates wget && \
addgroup -S claude && \
adduser -S -G claude -h /app claude && \
mkdir -p /app /data && \
chown -R claude:claude /app /data

WORKDIR /app

COPY --from=builder /out/claude-server /app/claude-server

USER claude

EXPOSE 62311
VOLUME ["/data"]

ENTRYPOINT ["/app/claude-server"]
CMD ["-no-browser", "-data-dir=/data"]
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
services:
claude-api:
build:
context: .
dockerfile: Dockerfile
args:
GOPROXY: "${GOPROXY:-https://proxy.golang.org,direct}"
image: claude-api:local
container_name: claude-api
restart: unless-stopped
ports:
- "${CLAUDE_API_PORT:-62311}:62311"
volumes:
- claude_api_data:/data
# 如果需要自定义配置,把示例复制为 ./docker/config.yaml 后取消下一行注释
# - ./docker/config.yaml:/data/config.yaml:ro
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:62311/healthz >/dev/null || exit 1"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s

volumes:
claude_api_data:
18 changes: 18 additions & 0 deletions docker/config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 复制为 docker/config.yaml 后,取消 docker-compose.yml 中的挂载注释即可生效。
# 注意: 容器默认以 -data-dir=/data 启动,所以配置文件必须挂载到 /data/config.yaml。

database:
type: mysql
mysql:
host: mysql
port: 3306
user: claude
password: change-me
database: claude_api
charset: utf8mb4

server:
host: 0.0.0.0
port: 62311

debug: false
208 changes: 208 additions & 0 deletions docs/docker-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Claude API Docker 部署手册

这份手册基于仓库当前实际启动方式整理,新增了 `Dockerfile` 和 `docker-compose.yml`,可以直接在本仓库里执行。

说明:

- 当前管理控制台使用固定版本 CDN 加载前端第三方库。
- 浏览器访问控制台时,需要能访问这些 CDN 资源。

## 1. 部署方式说明

- 默认方案使用 SQLite,不需要额外数据库,数据持久化在 Docker 卷 `claude_api_data` 中。
- 容器启动命令为 `./claude-server -no-browser -data-dir=/data`。
- 程序会把数据库、日志和可选的 `config.yaml` 都放在 `/data` 下。
- 服务默认端口为 `62311`,健康检查接口为 `/healthz`。

## 2. 前置条件

- Docker 24+ 或兼容版本
- Docker Compose v2
- 服务器放通 `62311` 端口,或由 Nginx/Caddy 做反向代理

检查环境:

```bash
docker --version
docker compose version
```

## 3. 快速部署

首次部署:

```bash
git clone https://github.com/kkddytd/claude-api.git
cd claude-api
docker compose up -d --build
```

如果你所在网络访问 `proxy.golang.org` 不稳定,可以直接切换 Go 模块代理后再构建:

```bash
GOPROXY=https://goproxy.cn,direct docker compose up -d --build
```

查看状态:

```bash
docker compose ps
docker compose logs -f claude-api
```

浏览器访问:

```text
http://localhost:62311
```

首次登录信息:

- 管理后台默认密码:`admin`
- 登录后建议立刻修改管理员密码
- 在“系统设置”中配置你的 API Key
- 在“账号管理”中添加 AWS Kiro 账号

## 4. 常用运维命令

启动或重建:

```bash
docker compose up -d --build
```

停止服务:

```bash
docker compose down
```

重启服务:

```bash
docker compose restart claude-api
```

查看实时日志:

```bash
docker compose logs -f claude-api
```

升级到仓库最新代码:

```bash
git pull
docker compose up -d --build
```

## 5. 数据与备份

容器内关键路径:

- `/data/data.sqlite3`:SQLite 数据库
- `/data/logs/`:服务日志
- `/data/config.yaml`:可选自定义配置文件

备份 SQLite:

```bash
mkdir -p backups
docker cp claude-api:/data/data.sqlite3 ./backups/data.sqlite3.$(date +%Y%m%d_%H%M%S)
```

检查数据目录:

```bash
docker compose exec claude-api sh -lc 'ls -lah /data && ls -lah /data/logs || true'
```

## 6. 自定义配置

如果你要改端口、启用 MySQL、打开调试模式,推荐通过 `config.yaml` 完成。

先复制示例配置:

```bash
cp docker/config.yaml.example docker/config.yaml
```

然后编辑 `docker/config.yaml`,再把 `docker-compose.yml` 里的这行取消注释:

```yaml
- ./docker/config.yaml:/data/config.yaml:ro
```

重要说明:

- 这个项目在传入 `-data-dir=/data` 后,会先切换工作目录到 `/data`,再读取 `config.yaml`。
- 所以配置文件必须挂载到 `/data/config.yaml`,而不是 `/app/config.yaml`。

如果你只想改宿主机映射端口,不改应用内部监听端口,可以直接这样启动:

```bash
CLAUDE_API_PORT=18080 docker compose up -d --build
```

此时访问地址变为:

```text
http://localhost:18080
```

## 7. 切换到 MySQL

项目本身支持 MySQL,`docker/config.yaml.example` 已经给了可直接改的模板。

你有两种做法:

1. 使用外部 MySQL 服务,把 `host` 改成真实地址。
2. 自己额外起一个 MySQL 容器,再把 `host` 改成该容器在同一网络下的服务名。

最少需要改这些字段:

- `database.type: mysql`
- `database.mysql.host`
- `database.mysql.port`
- `database.mysql.user`
- `database.mysql.password`
- `database.mysql.database`

改完后重新启动:

```bash
docker compose up -d --build
```

## 8. 反向代理建议

生产环境建议在外层加 Nginx 或 Caddy:

- 域名统一入口
- HTTPS 证书托管
- 更方便做访问控制
- 对流式响应要关闭代理缓冲

如果你已经有 Nginx,可以把上游指向:

```text
http://127.0.0.1:62311
```

## 9. 故障排查

健康检查:

```bash
curl http://127.0.0.1:62311/healthz
```

如果容器启动失败,优先检查:

- `docker compose logs -f claude-api`
- `docker/config.yaml` 是否是合法 YAML
- MySQL 连接信息是否正确
- 宿主机 `62311` 端口是否被占用
- 构建阶段如果出现 `proxy.golang.org ... unexpected EOF`,改用 `GOPROXY=https://goproxy.cn,direct`

如果只是想恢复默认配置,删除 `/data/config.yaml` 后重启容器即可。
2 changes: 1 addition & 1 deletion frontend/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import "embed"

// StaticFiles 嵌入所有前端静态文件
//
//go:embed *.html img/* css/*.css js/*.js vendor/css/*.css vendor/js/*.js vendor/fonts/*
//go:embed *.html img/* css/*.css js/*.js
var StaticFiles embed.FS
12 changes: 6 additions & 6 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
})();
</script>
<style>[v-cloak] { display: none !important; }</style>
<link href="/frontend/vendor/css/remixicon.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.9.1/fonts/remixicon.css" rel="stylesheet">

<!-- Vue 3 -->
<script src="/frontend/vendor/js/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.30/dist/vue.global.prod.js"></script>

<!-- Markdown & Highlight -->
<script src="/frontend/vendor/js/marked.min.js"></script>
<link rel="stylesheet" href="/frontend/vendor/css/github.min.css" id="hljs-light">
<link rel="stylesheet" href="/frontend/vendor/css/github-dark.min.css" id="hljs-dark" disabled>
<script src="/frontend/vendor/js/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@17.0.5/lib/marked.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.11.1/styles/github.min.css" id="hljs-light">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.11.1/styles/github-dark.min.css" id="hljs-dark" disabled>
<script src="https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>

<!-- 模块化CSS -->
<link rel="stylesheet" href="/frontend/css/variables.css">
Expand Down
4 changes: 2 additions & 2 deletions frontend/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<link href="/frontend/vendor/css/remixicon.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.9.1/fonts/remixicon.css" rel="stylesheet">
<style>
:root {
/* Claude-like Color Palette - Refined */
Expand Down Expand Up @@ -496,4 +496,4 @@ <h1>欢迎回来</h1>
</script>
</body>

</html>
</html>
41 changes: 41 additions & 0 deletions internal/database/license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package database

import (
"claude-api/internal/models"
"context"
"errors"

"gorm.io/gorm"
"gorm.io/gorm/clause"
)

const licenseCodeSettingKey = "license_code"

// SaveLicenseCode 保存激活码到 settings 表。
func (db *DB) SaveLicenseCode(ctx context.Context, code string) error {
setting := models.Setting{
Key: licenseCodeSettingKey,
Value: code,
}

return db.gorm.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "setting_key"}},
DoUpdates: clause.AssignmentColumns([]string{"setting_value"}),
}).Create(&setting).Error
}

// GetLicenseCode 从 settings 表读取激活码。
func (db *DB) GetLicenseCode(ctx context.Context) (string, error) {
var setting models.Setting
err := db.gorm.WithContext(ctx).
Where("setting_key = ?", licenseCodeSettingKey).
Take(&setting).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return "", nil
}
return "", err
}

return setting.Value, nil
}
1 change: 0 additions & 1 deletion internal/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type User struct {
TotalTokensUsed int64 `gorm:"column:total_tokens_used;default:0" json:"total_tokens_used"`
TotalRequests int64 `gorm:"column:total_requests;default:0" json:"total_requests"` // 总请求次数 @author ygw
TotalCostUSD float64 `gorm:"column:total_cost_usd;default:0" json:"total_cost_usd"` // 总消费金额(美元)@author ygw
ExpiresAt *int64 `gorm:"column:expires_at" json:"expires_at,omitempty"` // 过期时间(Unix时间戳),null表示永不过期 @author ygw
LastResetDaily *string `gorm:"column:last_reset_daily;size:50" json:"last_reset_daily,omitempty"`
LastResetMonthly *string `gorm:"column:last_reset_monthly;size:50" json:"last_reset_monthly,omitempty"`
ExpiresAt *int64 `gorm:"column:expires_at;index" json:"expires_at,omitempty"` // 过期时间(Unix时间戳),nil表示永不过期
Expand Down