Add Gitea CI, align Docker for multi-arch, and drop legacy YAML config.
CI / docker (push) Failing after 1m35s
CI / docker (push) Failing after 1m35s
Introduce Gitea Actions workflow and CI-aligned Dockerfile; remove file-based config in favor of SQLite settings; refresh branding and tighten gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-2
@@ -1,12 +1,34 @@
|
|||||||
|
# VCS / CI
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
.gitea
|
||||||
|
|
||||||
|
# Docs (not needed in image)
|
||||||
|
*.md
|
||||||
|
web/docs
|
||||||
|
|
||||||
|
# Local runtime / secrets
|
||||||
data/
|
data/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
luminary
|
luminary
|
||||||
|
luminary-recover
|
||||||
|
.docker-bin/
|
||||||
*.db
|
*.db
|
||||||
|
*.test
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Frontend (rebuilt in Dockerfile)
|
||||||
web/node_modules
|
web/node_modules
|
||||||
web/dist
|
web/dist
|
||||||
|
|
||||||
|
# IDE / editor
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
bifrost
|
|
||||||
.cursor
|
.cursor
|
||||||
*.md
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
# Gitea Actions CI 模板(Go + Vue)
|
||||||
|
|
||||||
|
复制整个 `.gitea/` 目录到新仓库即可启用 CI:**可选 go test** + **Docker 构建/推送**(Go 编译与 Vue 构建在 Dockerfile 内完成)。
|
||||||
|
|
||||||
|
## 项目约定
|
||||||
|
|
||||||
|
| 路径 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `go.mod` | 仓库根目录 |
|
||||||
|
| `Dockerfile` | 仓库根目录,多阶段构建(含前端 `web/`) |
|
||||||
|
| `web/` | 可选;由 Dockerfile 内 `npm run build` 处理,CI 不再单独构建 |
|
||||||
|
|
||||||
|
## Dockerfile 要求
|
||||||
|
|
||||||
|
工作流的 **Build image** 步骤在仓库根目录(或 Variable `DOCKERFILE` 指定路径)执行 `docker buildx build`,**不会**在 CI 里单独装 Node / 跑 `npm`。因此 Dockerfile 必须能独立完成构建与(运行时)启动。
|
||||||
|
|
||||||
|
### 基本要求
|
||||||
|
|
||||||
|
| 项 | 要求 |
|
||||||
|
|----|------|
|
||||||
|
| 位置 | 默认仓库根目录 `Dockerfile`;其他路径用 Variable `DOCKERFILE` |
|
||||||
|
| 构建上下文 | 仓库根目录 `.`(整个仓库会被 `COPY` 进镜像) |
|
||||||
|
| Go 版本 | `go.mod` 里 `go` 行与 `golang` 基础镜像大版本一致 |
|
||||||
|
| 多架构 | `go build` 须使用 `GOARCH="$TARGETARCH"`(buildx 注入);推荐 `CGO_ENABLED=0` |
|
||||||
|
| Go 模块代理 | **必须**在镜像内显式设置 `GOPROXY` / `GOSUMDB`(见下);容器内不会自动读取 `go.env` |
|
||||||
|
| 基础镜像加速 | 支持构建参数 `IMAGE_PREFIX`(CI 默认 `docker.1panel.live/library/`) |
|
||||||
|
| 前端(可选) | 存在 `web/` 时在 Dockerfile 内完成 `npm ci` + `npm run build` |
|
||||||
|
|
||||||
|
### CI 自动传入的构建参数
|
||||||
|
|
||||||
|
| 参数 | 默认 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `IMAGE_PREFIX` | `docker.1panel.live/library/` | 基础镜像前缀;`hub` 表示 Docker Hub |
|
||||||
|
| `GOPROXY` | `https://goproxy.cn,direct` | 与 workflow / Variable 一致 |
|
||||||
|
| `GOSUMDB` | `sum.golang.google.cn` | checksum 数据库 |
|
||||||
|
|
||||||
|
buildx 还会注入 `TARGETARCH`、`BUILDPLATFORM` 等,无需在 workflow 里写。
|
||||||
|
|
||||||
|
### 推荐:Go + Vue 多阶段模板
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
ARG IMAGE_PREFIX=
|
||||||
|
|
||||||
|
# 1) 前端(无 web/ 时可删整个 stage,并去掉 go-builder 里 COPY dist)
|
||||||
|
FROM --platform=$BUILDPLATFORM ${IMAGE_PREFIX}node:20-alpine AS web-builder
|
||||||
|
WORKDIR /src/web
|
||||||
|
COPY web/package.json web/package-lock.json web/.npmrc ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY web/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 2) Go 编译
|
||||||
|
FROM --platform=$BUILDPLATFORM ${IMAGE_PREFIX}golang:1.25-alpine AS go-builder
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG GOPROXY=https://goproxy.cn,direct
|
||||||
|
ARG GOSUMDB=sum.golang.google.cn
|
||||||
|
ENV GOPROXY=${GOPROXY} GOSUMDB=${GOSUMDB}
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod go mod download
|
||||||
|
|
||||||
|
COPY cmd/ ./cmd/
|
||||||
|
COPY internal/ ./internal/
|
||||||
|
# 若静态资源嵌入 Go:COPY --from=web-builder /src/web/dist/ ./path/to/static/
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" \
|
||||||
|
go build -ldflags="-w -s" -o /app ./cmd/yourapp
|
||||||
|
|
||||||
|
# 3) 运行镜像
|
||||||
|
FROM ${IMAGE_PREFIX}alpine:3.20
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
COPY --from=go-builder /app /usr/local/bin/yourapp
|
||||||
|
ENTRYPOINT ["/usr/local/bin/yourapp"]
|
||||||
|
```
|
||||||
|
|
||||||
|
按项目调整:`./cmd/yourapp`、静态资源路径、运行用户、`EXPOSE` / `VOLUME` 等。
|
||||||
|
|
||||||
|
### 仅 Go(无前端)
|
||||||
|
|
||||||
|
删除 `web-builder` stage;`go-builder` 中不要 `COPY` 前端产物;其余 `GOPROXY` / `TARGETARCH` / `IMAGE_PREFIX` 要求相同。
|
||||||
|
|
||||||
|
### 前端 npm 源(国内)
|
||||||
|
|
||||||
|
在 `web/.npmrc` 配置 registry,并在 Dockerfile 里与 `package.json` 一并 `COPY`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
registry=https://registry.npmmirror.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本地验证(与 CI 一致)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx build --platform linux/amd64 \
|
||||||
|
--build-arg IMAGE_PREFIX=docker.1panel.live/library/ \
|
||||||
|
--build-arg GOPROXY=https://goproxy.cn,direct \
|
||||||
|
-f Dockerfile -t myapp:test .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见 Dockerfile 构建错误
|
||||||
|
|
||||||
|
| 报错 | 原因 | 处理 |
|
||||||
|
|------|------|------|
|
||||||
|
| `proxy.golang.org` timeout | 镜像内未设 `ENV GOPROXY` | go-builder 阶段加 `ARG`/`ENV GOPROXY` |
|
||||||
|
| `node` / Vite 版本不符 | 基础镜像 Node 过旧 | 使用 `node:20-alpine` 及以上 |
|
||||||
|
| 某架构 build 失败 | 未使用 `TARGETARCH` | `GOARCH="$TARGETARCH"` |
|
||||||
|
| 拉基础镜像 timeout | Hub 不可达 | `--build-arg IMAGE_PREFIX=docker.1panel.live/library/` 或 1Panel 配镜像加速 |
|
||||||
|
|
||||||
|
## 一次性配置
|
||||||
|
|
||||||
|
1. **Runner**:部署 act_runner,标签含 `ubuntu-latest`,并挂载 `docker.sock`(见 [act-runner/README.md](act-runner/README.md))。
|
||||||
|
2. **Secret**:仓库 Settings → Actions → Secrets,添加 `REGISTRY_TOKEN`(PAT,`write:package` 权限)。
|
||||||
|
3. **Variable(推荐)**:若 runner 与 Gitea 同机、或 `gitea.server_url` 为内网地址,必须设置 `REGISTRY=你的公网域名`(如 `git.example.com`,**不要**填 `172.17.0.1:13827`)。
|
||||||
|
4. **Gitea Registry**:服务端 `[packages] ENABLED = true`,`ROOT_URL` 正确;穿透场景建议 `PUBLIC_URL_DETECTION = never`(Gitea 1.26+)。
|
||||||
|
|
||||||
|
`REGISTRY` 未设置时,会从 `GITEA_ROOT_URL` 或 `gitea.server_url` 推断;均为内网地址时 workflow 会提前失败并提示。
|
||||||
|
|
||||||
|
## 可选 Variables
|
||||||
|
|
||||||
|
仓库 Settings → Actions → Variables(留空则用默认值):
|
||||||
|
|
||||||
|
| 变量 | 默认 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `REGISTRY` | 见下方推断顺序 | **公网** Registry 主机名,如 `git.example.com`(勿用内网 IP:13827) |
|
||||||
|
| `GITEA_ROOT_URL` | (空) | 当 `gitea.server_url` 为内网时,可设 `https://git.example.com/` |
|
||||||
|
| `IMAGE_NAME` | 仓库名小写 | 镜像名,非 owner/repo 全路径 |
|
||||||
|
| `DOCKERFILE` | `Dockerfile` | Dockerfile 路径 |
|
||||||
|
| `DOCKER_PLATFORMS` | `linux/amd64,linux/arm64` | push 时 buildx 平台 |
|
||||||
|
| `GO_TEST_SCOPE` | `./...` | `go test` 包路径 |
|
||||||
|
| `RUN_GO_TEST` | (空,即运行) | 设 `false` 跳过 go test,仅 Docker 构建 |
|
||||||
|
| `DOCKER_IMAGE_PREFIX` | `1panel` | 基础镜像前缀;可选 `hub` / `daocloud` |
|
||||||
|
| `GOPROXY` | `https://goproxy.cn,direct` | Go 模块代理 |
|
||||||
|
| `GOSUMDB` | `sum.golang.google.cn` | Go checksum 数据库 |
|
||||||
|
|
||||||
|
## 触发与镜像 tag
|
||||||
|
|
||||||
|
- **pull_request**:go test(可关)+ 单架构 `docker build` 验证(不推送)
|
||||||
|
- **push main/master**:go test + 多架构构建并推送 `:latest`、`:sha-xxxxxxx`
|
||||||
|
- **push tag v\***:额外推送 `:v1.2.3` 等
|
||||||
|
|
||||||
|
示例(仓库 `rose_cat707/Prism`,Gitea 在 `git.example.com`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
git.example.com/rose_cat707/prism:latest
|
||||||
|
git.example.com/rose_cat707/prism:sha-35b3b48
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
.gitea/
|
||||||
|
├── README.md # 本文件
|
||||||
|
├── workflows/
|
||||||
|
│ └── ci.yml # 主工作流
|
||||||
|
└── act-runner/ # runner 部署参考(可选)
|
||||||
|
├── README.md
|
||||||
|
├── config.yaml
|
||||||
|
├── docker-compose.yml
|
||||||
|
└── .env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
## 本地验证 Registry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
host=$(echo "https://你的-gitea-地址/" | sed -e 's|^https://||' -e 's|/.*||')
|
||||||
|
curl -s -D - "https://${host}/v2/" -o /dev/null | grep -i www-authenticate
|
||||||
|
docker pull "${host}/owner/image:latest" # 公开 Registry 无需 login
|
||||||
|
```
|
||||||
|
|
||||||
|
推送镜像(CI)仍需仓库 Secret `REGISTRY_TOKEN`(`write:package`)。仅拉取公开包不需要登录。
|
||||||
|
|
||||||
|
`realm` 应指向公网 Gitea 域名,而非 `127.0.0.1`。
|
||||||
|
|
||||||
|
## 常见 Registry 错误
|
||||||
|
|
||||||
|
| 报错 | 原因 | 处理 |
|
||||||
|
|------|------|------|
|
||||||
|
| `Get "https://172.17.0.1:13827/v2/"` HTTP/HTTPS | Variable `REGISTRY` 或 `gitea.server_url` 为内网地址 | 设 `REGISTRY=git.example.com` |
|
||||||
|
| token 指向 `127.0.0.1` | Gitea `realm` 配置错误 | `PUBLIC_URL_DETECTION=never` + 正确 `ROOT_URL` |
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
GITEA_INSTANCE_URL=https://git.example.com
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN=从仓库_Settings_Actions_Runners_复制
|
||||||
|
GITEA_RUNNER_NAME=go-vue-ci-runner
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
# act_runner 配置参考
|
||||||
|
|
||||||
|
工作流 `runs-on: ubuntu-latest`;`docker` job 额外使用 `catthehacker/ubuntu:act-22.04` 容器(含 Docker CLI)。
|
||||||
|
|
||||||
|
## 构建镜像必须:挂载 Docker Socket
|
||||||
|
|
||||||
|
`docker` job 需要访问宿主机 Docker 引擎。在 **runner 所在机器** 的 `config.yaml` 中配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
container:
|
||||||
|
options: -v /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
valid_volumes:
|
||||||
|
- /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐 job 镜像(含 Node + Docker CLI):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
runner:
|
||||||
|
labels:
|
||||||
|
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
|
||||||
|
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后 **重启 runner**(例如 `docker restart <runner容器>` 或重启 Gitea Runner 服务)。
|
||||||
|
|
||||||
|
## 1Panel 宿主机:配置 Docker 镜像加速(推荐)
|
||||||
|
|
||||||
|
Runner 通过 `docker.sock` 使用宿主机 Docker。在 **1Panel** 中配置加速器后,普通 `docker pull` 会走加速;**buildx 多架构构建**仍建议配合 workflow 内的 `IMAGE_PREFIX`(默认 `docker.1panel.live`)。
|
||||||
|
|
||||||
|
1. 登录 1Panel → **容器** → **配置**
|
||||||
|
2. **镜像加速地址** 填入:
|
||||||
|
```text
|
||||||
|
https://docker.1panel.live
|
||||||
|
```
|
||||||
|
3. 保存并 **重启 Docker**
|
||||||
|
|
||||||
|
验证(在 runner 宿主机):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker info | grep -A5 'Registry Mirrors'
|
||||||
|
docker pull alpine:3.20
|
||||||
|
```
|
||||||
|
|
||||||
|
等价 `daemon.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registry-mirrors": ["https://docker.1panel.live"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CI 工作流默认 `IMAGE_PREFIX=docker.1panel.live/library/`(buildkit 拉基础镜像)。若 1Panel 加速不可用,可在仓库 Variables 设 `DOCKER_IMAGE_PREFIX=daocloud` 或 `hub`。
|
||||||
|
|
||||||
|
参考:[1Panel 容器配置文档](https://1panel.cn/docs/user_manual/containers/setting)
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
在 runner 宿主机执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker info
|
||||||
|
ls -l /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
## 从零部署 runner(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd .gitea/act-runner
|
||||||
|
cp .env.example .env # 填入 Registration Token
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Actions 镜像(替代 ghfast)
|
||||||
|
|
||||||
|
日志里出现 `git clone 'https://ghfast.top/https://github.com/actions/checkout'` 说明 **runner 宿主机** 的 `config.yaml` 配置了 `github_mirror`,与仓库 workflow 无关。
|
||||||
|
|
||||||
|
在 runner 的 `config.yaml` 中修改(修改后重启 runner):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
runner:
|
||||||
|
github_mirror: 'https://gitea.com' # 推荐
|
||||||
|
```
|
||||||
|
|
||||||
|
常用替代方案:
|
||||||
|
|
||||||
|
| `github_mirror` 值 | 说明 |
|
||||||
|
|------------------|------|
|
||||||
|
| `''`(留空) | 直连 `github.com`,网络可达时最简单 |
|
||||||
|
| `https://gitea.com` | Gitea 官方 actions 镜像,国内较稳 |
|
||||||
|
| `https://gitclone.com/github.com` | 第三方 GitHub 克隆镜像 |
|
||||||
|
| `https://ghfast.top/https://github.com` | 部分环境需代理认证,易报 `Proxy Authentication Required` |
|
||||||
|
|
||||||
|
前提:Gitea `app.ini` 中 `[actions] DEFAULT_ACTIONS_URL = github`(默认)。
|
||||||
|
|
||||||
|
**不依赖 runner 镜像** 的写法(workflow 内写绝对 URL,仅改写 `github.com` 的请求):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
```
|
||||||
|
|
||||||
|
本模板 `ci.yml` 已采用此写法。若其他 workflow 仍写 `uses: actions/checkout@v4`,仍需配置 `github_mirror` 或改为绝对 URL。
|
||||||
|
|
||||||
|
修改 `github_mirror` 后建议清理 runner 缓存目录(如 `/root/.cache/act`),否则旧缓存可能仍指向 ghfast。
|
||||||
|
|
||||||
|
## 常见错误
|
||||||
|
|
||||||
|
| 报错 | 原因 | 处理 |
|
||||||
|
|------|------|------|
|
||||||
|
| `registry-1.docker.io` i/o timeout | Docker Hub 不可达 | 1Panel 配 `docker.1panel.live`;Variable `DOCKER_IMAGE_PREFIX=1panel` |
|
||||||
|
| `ghfast.top` Proxy Authentication Required | runner `github_mirror` 指向 ghfast 且需代理认证 | 改 `github_mirror` 或删掉;见上文「GitHub Actions 镜像」 |
|
||||||
|
| `docker: command not found` | job 容器无 Docker CLI | 工作流已指定 act 镜像;或 runner 改用 catthehacker/ubuntu |
|
||||||
|
| `Cannot connect to Docker daemon` | 未挂载 docker.sock | 按上文修改 config.yaml 并重启 runner |
|
||||||
|
| `node not in PATH` | job 镜像无 Node | 标签映射改用 catthehacker/ubuntu:act-22.04 |
|
||||||
|
| `http: server gave HTTP response to HTTPS client` 且 token 指向 `127.0.0.1` | **Gitea Registry 配置/反代错误** | 见下文「Registry 登录失败」 |
|
||||||
|
|
||||||
|
## Registry 登录失败(127.0.0.1 / HTTP vs HTTPS)
|
||||||
|
|
||||||
|
若 `docker login git.rc707blog.top` 报错类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Get "https://127.0.0.1:xxxxx/v2/token?...": http: server gave HTTP response to HTTPS client
|
||||||
|
```
|
||||||
|
|
||||||
|
说明 Gitea 把 **Docker 认证 token 地址** 配成了本机内网地址,CI runner 访问不到。需在 **Gitea 服务器** 修复,而非改 workflow。
|
||||||
|
|
||||||
|
### 1. 检查 `app.ini`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[server]
|
||||||
|
ROOT_URL = https://git.example.com/
|
||||||
|
LOCAL_ROOT_URL = http://127.0.0.1:3000/
|
||||||
|
; 内网穿透 / 错误 Host 时(Gitea 1.26+):
|
||||||
|
; PUBLIC_URL_DETECTION = never
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
ENABLED = true
|
||||||
|
```
|
||||||
|
|
||||||
|
`ROOT_URL` 必须与浏览器访问 Gitea 的 **HTTPS 外网地址** 完全一致(含末尾 `/`)。
|
||||||
|
|
||||||
|
### 2. 反向代理必须转发 `/v2` 并带上头
|
||||||
|
|
||||||
|
Container Registry 固定使用根路径 `/v2`。Nginx 示例:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
client_max_body_size 0;
|
||||||
|
proxy_pass http://127.0.0.1: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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
关键:`X-Forwarded-Proto: https` 和正确的 `Host`(与 Gitea `ROOT_URL` 域名一致)。
|
||||||
|
|
||||||
|
### 3. 在 runner 宿主机验证(修复后)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GITEA_HOST=git.example.com # 改成你的 Gitea 域名
|
||||||
|
curl -s -D - "https://${GITEA_HOST}/v2/" -o /dev/null | grep -i www-authenticate
|
||||||
|
echo "$REGISTRY_TOKEN" | docker login "${GITEA_HOST}" -u 你的用户名 --password-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
应返回 `401 Unauthorized`(正常,表示 registry 可达)且 `docker login` 显示 **Login Succeeded**。
|
||||||
|
|
||||||
|
参考:[Gitea 反向代理文档](https://docs.gitea.com/administration/reverse-proxies)
|
||||||
|
|
||||||
|
## Gitea Runner v0.6.x(个人 runner)
|
||||||
|
|
||||||
|
1. 找到 runner 的配置文件或环境(安装目录 / docker compose)
|
||||||
|
2. 确保 runner 进程能访问宿主机 `/var/run/docker.sock`
|
||||||
|
3. Runners 页标签含 `ubuntu-latest` 且状态 **空闲/在线**
|
||||||
|
|
||||||
|
若使用 Gitea 网页注册的个人 runner(docker 模式),通常需在 runner 启动参数或 `config.yaml` 里加入 socket 挂载,具体路径取决于你的安装方式。
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# act_runner 配置(可选参考部署)
|
||||||
|
# 工作流 runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
|
||||||
|
runner:
|
||||||
|
file: .runner
|
||||||
|
capacity: 2
|
||||||
|
timeout: 3h
|
||||||
|
insecure: false
|
||||||
|
fetch_timeout: 5s
|
||||||
|
fetch_interval: 2s
|
||||||
|
# 拉取 uses: actions/checkout@v4 等 GitHub Action 时的镜像(替换 https://github.com)
|
||||||
|
# 需 Gitea app.ini 中 [actions] DEFAULT_ACTIONS_URL = github
|
||||||
|
# 留空则直连 github.com;第三方镜像不稳定时可改用 workflow 绝对 URL(见 .gitea/README.md)
|
||||||
|
#
|
||||||
|
# github_mirror: '' # 直连 GitHub
|
||||||
|
# github_mirror: 'https://gitea.com' # 推荐:Gitea 官方 actions 镜像
|
||||||
|
# github_mirror: 'https://gitclone.com/github.com' # 第三方 GitHub 克隆镜像
|
||||||
|
# github_mirror: 'https://ghfast.top/https://github.com' # 需代理认证时易失败,不推荐
|
||||||
|
labels:
|
||||||
|
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
|
||||||
|
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
container:
|
||||||
|
network: bridge
|
||||||
|
privileged: false
|
||||||
|
options: -v /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
valid_volumes:
|
||||||
|
- /var/run/docker.sock
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Gitea act_runner — 可选参考部署(标签 default,与工作流一致)
|
||||||
|
#
|
||||||
|
# 若已在 Gitea 注册个人/仓库 runner 且标签为 default,无需使用本目录。
|
||||||
|
|
||||||
|
services:
|
||||||
|
act-runner:
|
||||||
|
image: docker.io/gitea/act_runner:0.2.12
|
||||||
|
container_name: prism-act-runner
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
CONFIG_FILE: /config.yaml
|
||||||
|
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:-https://git.rc707blog.top}
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
|
||||||
|
GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME:-prism-ci-runner}
|
||||||
|
volumes:
|
||||||
|
- ./config.yaml:/config.yaml:ro
|
||||||
|
- act-runner-data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
working_dir: /data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
act-runner-data:
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# 通用 Go + Vue 项目 CI(Gitea Actions)
|
||||||
|
#
|
||||||
|
# 单 job:可选 go test + Docker 多架构构建/推送
|
||||||
|
# 约定:go.mod、Dockerfile(详见 .gitea/README.md「Dockerfile 要求」);前端(可选)在 web/
|
||||||
|
#
|
||||||
|
# 前置:act_runner(ubuntu-latest + docker.sock)、Secret REGISTRY_TOKEN
|
||||||
|
# Variables:REGISTRY、IMAGE_NAME、DOCKER_IMAGE_PREFIX、RUN_GO_TEST 等
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
tags: ['v*']
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ${{ vars.REGISTRY }}
|
||||||
|
GITEA_ROOT_URL: ${{ vars.GITEA_ROOT_URL }}
|
||||||
|
IMAGE_NAME: ${{ vars.IMAGE_NAME }}
|
||||||
|
DOCKERFILE: ${{ vars.DOCKERFILE }}
|
||||||
|
DOCKER_PLATFORMS: ${{ vars.DOCKER_PLATFORMS }}
|
||||||
|
GO_TEST_SCOPE: ${{ vars.GO_TEST_SCOPE }}
|
||||||
|
RUN_GO_TEST: ${{ vars.RUN_GO_TEST }}
|
||||||
|
DOCKER_IMAGE_PREFIX: ${{ vars.DOCKER_IMAGE_PREFIX }}
|
||||||
|
GOPROXY: ${{ vars.GOPROXY }}
|
||||||
|
GOSUMDB: ${{ vars.GOSUMDB }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Go tests
|
||||||
|
if: vars.RUN_GO_TEST != 'false'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
export GOPROXY="${GOPROXY:-https://goproxy.cn,direct}"
|
||||||
|
export GOSUMDB="${GOSUMDB:-sum.golang.google.cn}"
|
||||||
|
|
||||||
|
GO_VERSION=$(grep '^go ' go.mod | awk '{print $2}')
|
||||||
|
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
|
||||||
|
TAR="go${GO_VERSION}.linux-${ARCH}.tar.gz"
|
||||||
|
|
||||||
|
if ! command -v go >/dev/null 2>&1 || ! go version | grep -q "go${GO_VERSION} "; then
|
||||||
|
downloaded=0
|
||||||
|
for url in \
|
||||||
|
"https://mirrors.aliyun.com/golang/${TAR}" \
|
||||||
|
"https://golang.google.cn/dl/${TAR}" \
|
||||||
|
"https://go.dev/dl/${TAR}"; do
|
||||||
|
echo "trying ${url}"
|
||||||
|
if curl -fsSL --connect-timeout 20 --retry 3 --retry-delay 5 --max-time 600 \
|
||||||
|
"$url" -o /tmp/go.tgz; then
|
||||||
|
downloaded=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$downloaded" -eq 1 ] || { echo "failed to download Go ${GO_VERSION}" >&2; exit 1; }
|
||||||
|
rm -rf /usr/local/go
|
||||||
|
tar -C /usr/local -xzf /tmp/go.tgz
|
||||||
|
export PATH="/usr/local/go/bin:${PATH}"
|
||||||
|
fi
|
||||||
|
go version
|
||||||
|
go test "${GO_TEST_SCOPE:-./...}"
|
||||||
|
|
||||||
|
- name: Setup Docker
|
||||||
|
run: |
|
||||||
|
set -eux
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
apt-get update
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io
|
||||||
|
fi
|
||||||
|
if [ ! -S /var/run/docker.sock ]; then
|
||||||
|
echo "ERROR: /var/run/docker.sock 未挂载到 job 容器。" >&2
|
||||||
|
echo "请在 act_runner config.yaml 中配置:" >&2
|
||||||
|
echo " container.options: -v /var/run/docker.sock:/var/run/docker.sock" >&2
|
||||||
|
echo " container.valid_volumes: [/var/run/docker.sock]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker info
|
||||||
|
|
||||||
|
- name: Resolve registry
|
||||||
|
id: reg
|
||||||
|
if: gitea.event_name == 'push'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
url_to_host() {
|
||||||
|
echo "$1" | sed -e 's|^https://||' -e 's|^http://||' -e 's|/.*||'
|
||||||
|
}
|
||||||
|
|
||||||
|
is_internal_host() {
|
||||||
|
local host="${1%%:*}"
|
||||||
|
case "$host" in
|
||||||
|
localhost|127.*|10.*|192.168.*) return 0 ;;
|
||||||
|
172.*)
|
||||||
|
local second
|
||||||
|
second=$(echo "$host" | cut -d. -f2)
|
||||||
|
[ "$second" -ge 16 ] && [ "$second" -le 31 ]
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
registry="${REGISTRY:-}"
|
||||||
|
if [ -z "$registry" ] && [ -n "${GITEA_ROOT_URL:-}" ]; then
|
||||||
|
registry=$(url_to_host "${GITEA_ROOT_URL}")
|
||||||
|
fi
|
||||||
|
if [ -z "$registry" ]; then
|
||||||
|
registry=$(url_to_host "${GITEA_SERVER_URL}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_internal_host "$registry"; then
|
||||||
|
echo "ERROR: Registry 主机 '${registry}' 是内网地址。" >&2
|
||||||
|
echo "请设置 Variable REGISTRY=公网 Gitea 域名(如 git.example.com)。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "registry host: ${registry}"
|
||||||
|
|
||||||
|
auth_header=""
|
||||||
|
if auth_header=$(curl -fsS -D - "https://${registry}/v2/" -o /dev/null 2>&1 | grep -i '^www-authenticate:'); then
|
||||||
|
if echo "$auth_header" | grep -qiE '127\.0\.0\.1|172\.(1[6-9]|2[0-9]|3[01])\.|localhost|:13827'; then
|
||||||
|
echo "ERROR: Registry token realm 指向内网地址:${auth_header}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "host=${registry}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: Log in to Container Registry
|
||||||
|
if: gitea.event_name == 'push'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "${{ secrets.REGISTRY_TOKEN }}" | \
|
||||||
|
docker login "${{ steps.reg.outputs.host }}" -u "${{ gitea.actor }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_SHA: ${{ gitea.sha }}
|
||||||
|
GITEA_REF: ${{ gitea.ref }}
|
||||||
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
|
GITEA_REPOSITORY_OWNER: ${{ gitea.repository_owner }}
|
||||||
|
GITEA_EVENT_NAME: ${{ gitea.event_name }}
|
||||||
|
REGISTRY_HOST: ${{ steps.reg.outputs.host }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
dockerfile="${DOCKERFILE:-Dockerfile}"
|
||||||
|
|
||||||
|
if [ "${DOCKER_IMAGE_PREFIX:-}" = "hub" ] || [ "${DOCKER_IMAGE_PREFIX:-}" = "docker.io" ]; then
|
||||||
|
image_prefix=""
|
||||||
|
elif [ -z "${DOCKER_IMAGE_PREFIX:-}" ] || [ "${DOCKER_IMAGE_PREFIX}" = "1panel" ]; then
|
||||||
|
image_prefix="docker.1panel.live/library/"
|
||||||
|
elif [ "${DOCKER_IMAGE_PREFIX}" = "daocloud" ]; then
|
||||||
|
image_prefix="docker.m.daocloud.io/library/"
|
||||||
|
else
|
||||||
|
image_prefix="${DOCKER_IMAGE_PREFIX}"
|
||||||
|
[[ "${image_prefix}" == */ ]] || image_prefix="${image_prefix}/"
|
||||||
|
fi
|
||||||
|
echo "using IMAGE_PREFIX=${image_prefix:-<docker.io>}"
|
||||||
|
build_args=(
|
||||||
|
--build-arg "IMAGE_PREFIX=${image_prefix}"
|
||||||
|
--build-arg "GOPROXY=${GOPROXY:-https://goproxy.cn,direct}"
|
||||||
|
--build-arg "GOSUMDB=${GOSUMDB:-sum.golang.google.cn}"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder_id="gitea-buildx-${GITEA_REPOSITORY//\//-}"
|
||||||
|
|
||||||
|
if [ "${GITEA_EVENT_NAME}" = "push" ]; then
|
||||||
|
install_binfmt() {
|
||||||
|
for img in \
|
||||||
|
"docker.1panel.live/tonistiigi/binfmt:latest" \
|
||||||
|
"docker.m.daocloud.io/tonistiigi/binfmt:latest" \
|
||||||
|
"tonistiigi/binfmt:latest"; do
|
||||||
|
echo "trying binfmt image: ${img}"
|
||||||
|
if docker run --privileged --rm "${img}" --install all; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
install_binfmt || true
|
||||||
|
|
||||||
|
registry="${REGISTRY_HOST}"
|
||||||
|
image_name="${IMAGE_NAME:-$(echo "${GITEA_REPOSITORY##*/}" | tr '[:upper:]' '[:lower:]')}"
|
||||||
|
platforms="${DOCKER_PLATFORMS:-linux/amd64,linux/arm64}"
|
||||||
|
image="${registry}/${GITEA_REPOSITORY_OWNER}/${image_name}"
|
||||||
|
tags="${image}:sha-${GITEA_SHA:0:7}"
|
||||||
|
case "${GITEA_REF}" in
|
||||||
|
refs/heads/main|refs/heads/master)
|
||||||
|
tags="${tags},${image}:latest"
|
||||||
|
;;
|
||||||
|
refs/tags/v*)
|
||||||
|
tags="${tags},${image}:${GITEA_REF#refs/tags/}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
docker buildx create --name "${builder_id}" --use 2>/dev/null || docker buildx use "${builder_id}"
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
|
||||||
|
tag_args=()
|
||||||
|
IFS=',' read -r -a tag_list <<< "$tags"
|
||||||
|
for t in "${tag_list[@]}"; do
|
||||||
|
tag_args+=(-t "$t")
|
||||||
|
done
|
||||||
|
|
||||||
|
docker buildx build \
|
||||||
|
--platform "${platforms}" \
|
||||||
|
-f "${dockerfile}" \
|
||||||
|
"${build_args[@]}" \
|
||||||
|
"${tag_args[@]}" \
|
||||||
|
--push \
|
||||||
|
.
|
||||||
|
else
|
||||||
|
# PR:仅单架构构建验证 Dockerfile,不推送
|
||||||
|
docker buildx create --name "${builder_id}" --use 2>/dev/null || docker buildx use "${builder_id}"
|
||||||
|
docker buildx inspect --bootstrap
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
-f "${dockerfile}" \
|
||||||
|
"${build_args[@]}" \
|
||||||
|
--load \
|
||||||
|
.
|
||||||
|
fi
|
||||||
+25
-24
@@ -1,41 +1,42 @@
|
|||||||
# ---> Go
|
# Binaries
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
/luminary
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
/luminary-recover
|
||||||
#
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Go
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
# Go workspace file
|
|
||||||
go.work
|
go.work
|
||||||
go.work.sum
|
go.work.sum
|
||||||
|
*.out
|
||||||
|
coverage/
|
||||||
|
*.coverprofile
|
||||||
|
|
||||||
# env file
|
# Luminary runtime data
|
||||||
.env
|
data/
|
||||||
|
|
||||||
# Luminary runtime
|
|
||||||
/data/
|
/data/
|
||||||
/luminary
|
|
||||||
.docker-bin/
|
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
# Node
|
# Docker local prebuilt
|
||||||
web/node_modules/
|
.docker-bin/
|
||||||
web/dist/assets/
|
|
||||||
|
|
||||||
# IDE
|
# Frontend build output
|
||||||
|
web/dist/
|
||||||
|
web/node_modules/
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# IDE / editor
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.cursor/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|||||||
+51
-26
@@ -1,43 +1,68 @@
|
|||||||
# Multi-stage: build frontend + Go binaries at image build time; runtime only executes.
|
# Luminary AI Gateway(Go + Vue 管理台)
|
||||||
ARG NODE_MAJOR=20
|
#
|
||||||
ARG DOCKER_REGISTRY=docker.1panel.live/library/
|
# 构建:
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
# docker build -t luminary:latest .
|
||||||
|
#
|
||||||
|
# 运行:
|
||||||
|
# docker run -d --name luminary \
|
||||||
|
# -p 8293:8293 \
|
||||||
|
# -v luminary-data:/data \
|
||||||
|
# luminary:latest
|
||||||
|
#
|
||||||
|
# 生产镜像由 Gitea Actions 构建,见 README 与 .gitea/README.md
|
||||||
|
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# 基础镜像默认 Docker Hub;CI 默认 1Panel 加速(docker.1panel.live),本地可传:
|
||||||
|
# docker build --build-arg IMAGE_PREFIX=docker.1panel.live/library/ -t luminary:latest .
|
||||||
|
ARG IMAGE_PREFIX=
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM ${IMAGE_PREFIX}node:20-alpine AS web-builder
|
||||||
|
|
||||||
FROM ${DOCKER_REGISTRY}node:${NODE_MAJOR}-alpine AS web
|
|
||||||
WORKDIR /src/web
|
WORKDIR /src/web
|
||||||
ARG NPM_REGISTRY=https://registry.npmmirror.com
|
COPY web/package.json web/package-lock.json web/.npmrc ./
|
||||||
COPY web/package.json web/package-lock.json ./
|
RUN npm ci
|
||||||
RUN npm ci --registry="${NPM_REGISTRY}"
|
COPY web/src web/src
|
||||||
COPY web/ ./
|
COPY web/public web/public
|
||||||
|
COPY web/index.html web/tsconfig.json web/vite.config.ts ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
ARG DOCKER_REGISTRY=docker.1panel.live/library/
|
FROM --platform=$BUILDPLATFORM ${IMAGE_PREFIX}golang:1.25-alpine AS go-builder
|
||||||
FROM ${DOCKER_REGISTRY}golang:1.25-alpine AS gobuild
|
|
||||||
WORKDIR /src
|
ARG TARGETARCH=amd64
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
ARG GOPROXY=https://goproxy.cn,direct
|
||||||
ARG GOSUMDB=sum.golang.google.cn
|
ARG GOSUMDB=sum.golang.google.cn
|
||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 GOPROXY=${GOPROXY} GOSUMDB=${GOSUMDB}
|
||||||
GOPROXY=${GOPROXY} \
|
|
||||||
GOSUMDB=${GOSUMDB}
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY . .
|
|
||||||
COPY --from=web /src/web/dist ./web/dist
|
|
||||||
RUN go build -trimpath -ldflags="-s -w" -o /out/luminary ./cmd/luminary \
|
|
||||||
&& go build -trimpath -ldflags="-s -w" -o /out/luminary-recover ./cmd/luminary-recover
|
|
||||||
|
|
||||||
ARG DOCKER_REGISTRY=docker.1panel.live/library/
|
WORKDIR /src
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
COPY cmd/ ./cmd/
|
||||||
|
COPY internal/ ./internal/
|
||||||
|
COPY web/static_embed.go ./web/
|
||||||
|
COPY --from=web-builder /src/web/dist ./web/dist
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
GOOS=linux GOARCH="$TARGETARCH" \
|
||||||
|
go build -trimpath -ldflags="-w -s" -o /luminary ./cmd/luminary \
|
||||||
|
&& go build -trimpath -ldflags="-w -s" -o /luminary-recover ./cmd/luminary-recover
|
||||||
|
|
||||||
|
ARG IMAGE_PREFIX=
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
ARG APK_MIRROR=mirrors.aliyun.com
|
||||||
FROM ${DOCKER_REGISTRY}alpine:3.20 AS runtime
|
FROM ${IMAGE_PREFIX}alpine:3.20
|
||||||
|
|
||||||
ARG APK_MIRROR
|
ARG APK_MIRROR
|
||||||
RUN sed -i "s/dl-cdn.alpinelinux.org/${APK_MIRROR}/g" /etc/apk/repositories \
|
RUN sed -i "s/dl-cdn.alpinelinux.org/${APK_MIRROR}/g" /etc/apk/repositories \
|
||||||
&& apk add --no-cache ca-certificates tini
|
&& apk add --no-cache ca-certificates tzdata tini
|
||||||
|
|
||||||
ENV LUMINARY_DATA=/data \
|
ENV LUMINARY_DATA=/data \
|
||||||
LUMINARY_ADDR=:8293
|
LUMINARY_ADDR=:8293
|
||||||
|
|
||||||
COPY --from=gobuild /out/luminary /usr/local/bin/luminary
|
COPY --from=go-builder /luminary /usr/local/bin/luminary
|
||||||
COPY --from=gobuild /out/luminary-recover /usr/local/bin/luminary-recover
|
COPY --from=go-builder /luminary-recover /usr/local/bin/luminary-recover
|
||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
COPY scripts/luminary-recover.sh /usr/local/bin/luminary-recover.sh
|
COPY scripts/luminary-recover.sh /usr/local/bin/luminary-recover.sh
|
||||||
RUN chmod +x /entrypoint.sh /usr/local/bin/luminary-recover.sh
|
RUN chmod +x /entrypoint.sh /usr/local/bin/luminary-recover.sh
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Luminary Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
export GOPROXY ?= https://goproxy.cn,direct
|
export GOPROXY ?= https://goproxy.cn,direct
|
||||||
export GOSUMDB ?= sum.golang.google.cn
|
export GOSUMDB ?= sum.golang.google.cn
|
||||||
export GOPRIVATE ?= git.rc707blog.top
|
export GOPRIVATE ?= git.rc707blog.top
|
||||||
export DOCKER_REGISTRY ?= docker.1panel.live/library/
|
export IMAGE_PREFIX ?= docker.1panel.live/library/
|
||||||
export APK_MIRROR ?= mirrors.aliyun.com
|
export APK_MIRROR ?= mirrors.aliyun.com
|
||||||
export DOCKER_PLATFORM ?= linux/amd64
|
export DOCKER_PLATFORM ?= linux/amd64
|
||||||
export LUMINARY_IMAGE ?= registry.rc707blog.top/rose_cat707/luminary:latest
|
export LUMINARY_IMAGE ?= registry.rc707blog.top/rose_cat707/luminary:latest
|
||||||
@@ -33,7 +33,7 @@ test:
|
|||||||
docker-build:
|
docker-build:
|
||||||
docker buildx build --platform $(DOCKER_PLATFORM) \
|
docker buildx build --platform $(DOCKER_PLATFORM) \
|
||||||
-t $(LUMINARY_IMAGE) \
|
-t $(LUMINARY_IMAGE) \
|
||||||
--build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
|
--build-arg IMAGE_PREFIX=$(IMAGE_PREFIX) \
|
||||||
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
||||||
--build-arg GOPROXY=$(GOPROXY) \
|
--build-arg GOPROXY=$(GOPROXY) \
|
||||||
--build-arg GOSUMDB=$(GOSUMDB) \
|
--build-arg GOSUMDB=$(GOSUMDB) \
|
||||||
@@ -43,7 +43,7 @@ docker-build:
|
|||||||
docker-push:
|
docker-push:
|
||||||
docker buildx build --platform $(DOCKER_PLATFORM) \
|
docker buildx build --platform $(DOCKER_PLATFORM) \
|
||||||
-t $(LUMINARY_IMAGE) \
|
-t $(LUMINARY_IMAGE) \
|
||||||
--build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
|
--build-arg IMAGE_PREFIX=$(IMAGE_PREFIX) \
|
||||||
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
||||||
--build-arg GOPROXY=$(GOPROXY) \
|
--build-arg GOPROXY=$(GOPROXY) \
|
||||||
--build-arg GOSUMDB=$(GOSUMDB) \
|
--build-arg GOSUMDB=$(GOSUMDB) \
|
||||||
@@ -57,7 +57,7 @@ docker-push-prebuilt: build-web
|
|||||||
docker buildx build --platform $(DOCKER_PLATFORM) \
|
docker buildx build --platform $(DOCKER_PLATFORM) \
|
||||||
-f docker/Dockerfile.prebuilt \
|
-f docker/Dockerfile.prebuilt \
|
||||||
-t $(LUMINARY_IMAGE) \
|
-t $(LUMINARY_IMAGE) \
|
||||||
--build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
|
--build-arg IMAGE_PREFIX=$(IMAGE_PREFIX) \
|
||||||
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
--build-arg APK_MIRROR=$(APK_MIRROR) \
|
||||||
--push \
|
--push \
|
||||||
.
|
.
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ cd web && npm install && npm run dev
|
|||||||
| `-data-dir` / `LUMINARY_DATA` | SQLite 数据目录 | `./data` |
|
| `-data-dir` / `LUMINARY_DATA` | SQLite 数据目录 | `./data` |
|
||||||
| `-addr` / `LUMINARY_ADDR` | HTTP 监听地址 | `:8293` |
|
| `-addr` / `LUMINARY_ADDR` | HTTP 监听地址 | `:8293` |
|
||||||
|
|
||||||
> 若存在旧版 `configs/config.yaml` 或 `/data/config.yaml`,**仅在首次初始化系统设置时**会尝试导入其中的加密密钥等项(导入失败不阻塞启动)。
|
可选环境变量 `LUMINARY_ENCRYPTION_KEY`:仅在首次初始化系统设置时写入加密密钥(一般无需设置,管理台可改)。
|
||||||
|
|
||||||
## Docker 部署
|
## Docker 部署
|
||||||
|
|
||||||
@@ -152,7 +152,34 @@ LUMINARY_IMAGE=registry.example.com/luminary:v1.0.0 make docker-push
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
`docker-compose.yml` 通过环境变量 `LUMINARY_IMAGE` 指定镜像名(默认 `luminary:latest`)。更新版本需重新 `docker build` / `docker push` 后重启容器。
|
`docker-compose.yml` 通过环境变量 `LUMINARY_IMAGE` 指定镜像名(默认 `registry.rc707blog.top/rose_cat707/luminary:latest`)。更新版本需重新 `docker build` / `docker push` 后重启容器。
|
||||||
|
|
||||||
|
### Gitea Actions CI
|
||||||
|
|
||||||
|
生产镜像由 Gitea Actions 自动构建并推送到 Container Registry,工作流见 [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml),详细配置见 [`.gitea/README.md`](.gitea/README.md)。
|
||||||
|
|
||||||
|
**一次性配置**(仓库 Settings → Actions):
|
||||||
|
|
||||||
|
| 类型 | 名称 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Secret | `REGISTRY_TOKEN` | Gitea PAT,需 `write:package` 权限 |
|
||||||
|
| Variable | `REGISTRY` | 公网 Gitea 域名,如 `git.rc707blog.top`(勿用内网 IP) |
|
||||||
|
|
||||||
|
**触发与镜像 tag**:
|
||||||
|
|
||||||
|
- `pull_request`:go test + 单架构 Docker 构建验证(不推送)
|
||||||
|
- `push main/master`:多架构构建并推送 `:latest`、`:sha-xxxxxxx`
|
||||||
|
- `push tag v*`:额外推送 `:v1.2.3`
|
||||||
|
|
||||||
|
示例镜像地址(仓库 `rose_cat707/luminary`,Gitea 在 `git.rc707blog.top`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
git.rc707blog.top/rose_cat707/luminary:latest
|
||||||
|
git.rc707blog.top/rose_cat707/luminary:sha-35b3b48
|
||||||
|
git.rc707blog.top/rose_cat707/luminary:v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
部署时设置 `LUMINARY_IMAGE` 指向 CI 推送的镜像即可。
|
||||||
|
|
||||||
## 应急恢复(容器内)
|
## 应急恢复(容器内)
|
||||||
|
|
||||||
@@ -189,7 +216,7 @@ go run ./cmd/luminary-recover --all
|
|||||||
| `--clear-ip` | 删除全部 IP 规则 |
|
| `--clear-ip` | 删除全部 IP 规则 |
|
||||||
| `--clear-sessions` | 删除全部管理员 Session |
|
| `--clear-sessions` | 删除全部管理员 Session |
|
||||||
| `--restart` | 向 PID 1 发送 SIGTERM,由容器重启策略拉起新进程(清除登录冷却) |
|
| `--restart` | 向 PID 1 发送 SIGTERM,由容器重启策略拉起新进程(清除登录冷却) |
|
||||||
| `--password` | 新密码(默认 `admin123`;若存在旧版 config 则尝试读取其中 `admin.password`) |
|
| `--password` | 新密码(默认 `admin123`) |
|
||||||
| `--username` | 管理员用户名(默认 `admin`) |
|
| `--username` | 管理员用户名(默认 `admin`) |
|
||||||
| `-data-dir` / `LUMINARY_DATA` | 数据目录(默认 `./data`,容器内为 `/data`) |
|
| `-data-dir` / `LUMINARY_DATA` | 数据目录(默认 `./data`,容器内为 `/data`) |
|
||||||
|
|
||||||
@@ -201,7 +228,6 @@ cmd/luminary-recover/ 应急恢复 CLI(容器内 docker exec 使用)
|
|||||||
scripts/ 运维脚本(含 luminary-recover.sh)
|
scripts/ 运维脚本(含 luminary-recover.sh)
|
||||||
internal/ Go 后端(gateway、store、handler...)
|
internal/ Go 后端(gateway、store、handler...)
|
||||||
web/ Vue 3 前端(构建产物嵌入二进制)
|
web/ Vue 3 前端(构建产物嵌入二进制)
|
||||||
configs/ 旧版配置示例(可选,仅用于首次迁移导入)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# 已废弃:Luminary 不再依赖配置文件启动,请在管理台「系统设置」维护参数。
|
|
||||||
# 本文件仅用于从旧版部署首次迁移时自动导入(若存在且数据库尚无 system_settings 记录)。
|
|
||||||
server:
|
|
||||||
addr: ":8293"
|
|
||||||
|
|
||||||
database:
|
|
||||||
path: "./data/luminary.db"
|
|
||||||
|
|
||||||
admin:
|
|
||||||
username: "admin"
|
|
||||||
password: "admin123"
|
|
||||||
|
|
||||||
cache:
|
|
||||||
usage_flush_interval: 5s
|
|
||||||
|
|
||||||
health_check:
|
|
||||||
interval: 30s
|
|
||||||
|
|
||||||
security:
|
|
||||||
# 生产环境必须修改;容器首次启动会自动生成随机密钥
|
|
||||||
# 也可通过环境变量覆盖:encryption_key: "env.LUMINARY_ENCRYPTION_KEY"
|
|
||||||
encryption_key: "change-me-in-production-32bytes!!"
|
|
||||||
session_ttl: 24h
|
|
||||||
login_max_attempts: 5
|
|
||||||
login_lockout: 15m
|
|
||||||
# 仅当直连来源为以下 IP/CIDR 时才信任 X-Forwarded-For / X-Real-IP(反代部署时配置)
|
|
||||||
trusted_proxies: []
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
max_retries: 3
|
|
||||||
retry_backoff_ms: 200
|
|
||||||
+2
-2
@@ -4,10 +4,10 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
DOCKER_REGISTRY: docker.1panel.live/library/
|
IMAGE_PREFIX: docker.1panel.live/library/
|
||||||
APK_MIRROR: mirrors.aliyun.com
|
APK_MIRROR: mirrors.aliyun.com
|
||||||
NPM_REGISTRY: https://registry.npmmirror.com
|
|
||||||
GOPROXY: https://goproxy.cn,direct
|
GOPROXY: https://goproxy.cn,direct
|
||||||
|
GOSUMDB: sum.golang.google.cn
|
||||||
image: ${LUMINARY_IMAGE:-registry.rc707blog.top/rose_cat707/luminary:latest}
|
image: ${LUMINARY_IMAGE:-registry.rc707blog.top/rose_cat707/luminary:latest}
|
||||||
container_name: luminary
|
container_name: luminary
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Runtime image using locally cross-compiled linux/amd64 binaries (avoids QEMU segfault in buildx).
|
# Runtime image using locally cross-compiled linux/amd64 binaries (avoids QEMU segfault in buildx).
|
||||||
ARG DOCKER_REGISTRY=docker.1panel.live/library/
|
ARG IMAGE_PREFIX=docker.1panel.live/library/
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
ARG APK_MIRROR=mirrors.aliyun.com
|
||||||
FROM ${DOCKER_REGISTRY}alpine:3.20 AS runtime
|
FROM ${IMAGE_PREFIX}alpine:3.20 AS runtime
|
||||||
ARG APK_MIRROR
|
ARG APK_MIRROR
|
||||||
RUN sed -i "s/dl-cdn.alpinelinux.org/${APK_MIRROR}/g" /etc/apk/repositories \
|
RUN sed -i "s/dl-cdn.alpinelinux.org/${APK_MIRROR}/g" /etc/apk/repositories \
|
||||||
&& apk add --no-cache ca-certificates tini
|
&& apk add --no-cache ca-certificates tini
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
golang.org/x/crypto v0.53.0
|
golang.org/x/crypto v0.53.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,8 +25,6 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -74,8 +70,6 @@ github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRC
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -109,8 +103,6 @@ golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
|||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Server ServerConfig `yaml:"server"`
|
|
||||||
Database DatabaseConfig `yaml:"database"`
|
|
||||||
Admin AdminConfig `yaml:"admin"`
|
|
||||||
Cache CacheConfig `yaml:"cache"`
|
|
||||||
HealthCheck HealthCheckConfig `yaml:"health_check"`
|
|
||||||
Security SecurityConfig `yaml:"security"`
|
|
||||||
Gateway GatewayConfig `yaml:"gateway"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GatewayConfig struct {
|
|
||||||
MaxRetries int `yaml:"max_retries"`
|
|
||||||
RetryBackoffMs int `yaml:"retry_backoff_ms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
Addr string `yaml:"addr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
Path string `yaml:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AdminConfig struct {
|
|
||||||
Username string `yaml:"username"`
|
|
||||||
Password string `yaml:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CacheConfig struct {
|
|
||||||
UsageFlushInterval time.Duration `yaml:"usage_flush_interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HealthCheckConfig struct {
|
|
||||||
Interval time.Duration `yaml:"interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecurityConfig struct {
|
|
||||||
EncryptionKey string `yaml:"encryption_key"`
|
|
||||||
SessionTTL time.Duration `yaml:"session_ttl"`
|
|
||||||
IPAllowlist []string `yaml:"ip_allowlist"`
|
|
||||||
TrustedProxies []string `yaml:"trusted_proxies"`
|
|
||||||
LoginMaxAttempts int `yaml:"login_max_attempts"`
|
|
||||||
LoginLockout time.Duration `yaml:"login_lockout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Load(path string) (*Config, error) {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read config: %w", err)
|
|
||||||
}
|
|
||||||
cfg := Default()
|
|
||||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
|
||||||
return nil, fmt.Errorf("parse config: %w", err)
|
|
||||||
}
|
|
||||||
resolveEnv(cfg)
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Default() *Config {
|
|
||||||
return &Config{
|
|
||||||
Server: ServerConfig{Addr: ":8080"},
|
|
||||||
Database: DatabaseConfig{
|
|
||||||
Path: "./data/luminary.db",
|
|
||||||
},
|
|
||||||
Admin: AdminConfig{
|
|
||||||
Username: "admin",
|
|
||||||
Password: "admin123",
|
|
||||||
},
|
|
||||||
Cache: CacheConfig{
|
|
||||||
UsageFlushInterval: 5 * time.Second,
|
|
||||||
},
|
|
||||||
HealthCheck: HealthCheckConfig{
|
|
||||||
Interval: 30 * time.Second,
|
|
||||||
},
|
|
||||||
Security: SecurityConfig{
|
|
||||||
EncryptionKey: "change-me-in-production-32bytes!!",
|
|
||||||
SessionTTL: 24 * time.Hour,
|
|
||||||
LoginMaxAttempts: 5,
|
|
||||||
LoginLockout: 15 * time.Minute,
|
|
||||||
},
|
|
||||||
Gateway: GatewayConfig{
|
|
||||||
MaxRetries: 3,
|
|
||||||
RetryBackoffMs: 200,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveEnv(cfg *Config) {
|
|
||||||
cfg.Admin.Username = resolveEnvValue(cfg.Admin.Username)
|
|
||||||
cfg.Admin.Password = resolveEnvValue(cfg.Admin.Password)
|
|
||||||
cfg.Security.EncryptionKey = resolveEnvValue(cfg.Security.EncryptionKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveEnvValue(v string) string {
|
|
||||||
if strings.HasPrefix(v, "env.") {
|
|
||||||
key := strings.TrimPrefix(v, "env.")
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rose_cat707/luminary/internal/auth"
|
"github.com/rose_cat707/luminary/internal/auth"
|
||||||
"github.com/rose_cat707/luminary/internal/config"
|
|
||||||
"github.com/rose_cat707/luminary/internal/model"
|
"github.com/rose_cat707/luminary/internal/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -25,13 +24,6 @@ func encryptionKeyCandidates() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add(os.Getenv("LUMINARY_ENCRYPTION_KEY"))
|
add(os.Getenv("LUMINARY_ENCRYPTION_KEY"))
|
||||||
for _, path := range legacyConfigPaths() {
|
|
||||||
cfg, err := config.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
add(cfg.Security.EncryptionKey)
|
|
||||||
}
|
|
||||||
add(legacyDefaultEncryptionKey)
|
add(legacyDefaultEncryptionKey)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,22 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rose_cat707/luminary/internal/config"
|
|
||||||
"github.com/rose_cat707/luminary/internal/model"
|
"github.com/rose_cat707/luminary/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const legacyDefaultEncryptionKey = "change-me-in-production-32bytes!!"
|
const legacyDefaultEncryptionKey = "change-me-in-production-32bytes!!"
|
||||||
|
|
||||||
func legacyConfigPaths() []string {
|
|
||||||
var paths []string
|
|
||||||
if p := os.Getenv("LUMINARY_CONFIG"); p != "" {
|
|
||||||
paths = append(paths, p)
|
|
||||||
}
|
|
||||||
paths = append(paths, "configs/config.yaml", "/data/config.yaml")
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeLegacySettings(s model.SystemSettings) model.SystemSettings {
|
func mergeLegacySettings(s model.SystemSettings) model.SystemSettings {
|
||||||
for _, path := range legacyConfigPaths() {
|
|
||||||
cfg, err := config.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key := strings.TrimSpace(cfg.Security.EncryptionKey)
|
|
||||||
if key != "" {
|
|
||||||
s.EncryptionKey = key
|
|
||||||
}
|
|
||||||
if cfg.Security.SessionTTL > 0 {
|
|
||||||
s.SessionTTLSeconds = int64(cfg.Security.SessionTTL.Seconds())
|
|
||||||
}
|
|
||||||
if cfg.Security.LoginMaxAttempts > 0 {
|
|
||||||
s.LoginMaxAttempts = cfg.Security.LoginMaxAttempts
|
|
||||||
}
|
|
||||||
if cfg.Security.LoginLockout > 0 {
|
|
||||||
s.LoginLockoutSeconds = int64(cfg.Security.LoginLockout.Seconds())
|
|
||||||
}
|
|
||||||
if len(cfg.Security.TrustedProxies) > 0 {
|
|
||||||
b, _ := json.Marshal(cfg.Security.TrustedProxies)
|
|
||||||
s.TrustedProxiesJSON = string(b)
|
|
||||||
}
|
|
||||||
if cfg.Cache.UsageFlushInterval > 0 {
|
|
||||||
s.UsageFlushSeconds = int64(cfg.Cache.UsageFlushInterval.Seconds())
|
|
||||||
}
|
|
||||||
if cfg.HealthCheck.Interval > 0 {
|
|
||||||
s.HealthCheckSeconds = int64(cfg.HealthCheck.Interval.Seconds())
|
|
||||||
}
|
|
||||||
if cfg.Gateway.MaxRetries > 0 {
|
|
||||||
s.GatewayMaxRetries = cfg.Gateway.MaxRetries
|
|
||||||
}
|
|
||||||
if cfg.Gateway.RetryBackoffMs >= 0 {
|
|
||||||
s.GatewayRetryBackoffMs = cfg.Gateway.RetryBackoffMs
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if key := strings.TrimSpace(os.Getenv("LUMINARY_ENCRYPTION_KEY")); key != "" {
|
if key := strings.TrimSpace(os.Getenv("LUMINARY_ENCRYPTION_KEY")); key != "" {
|
||||||
s.EncryptionKey = key
|
s.EncryptionKey = key
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyAdminCredentials returns admin bootstrap credentials from an optional legacy config file.
|
// LegacyAdminCredentials returns default bootstrap credentials for first admin.
|
||||||
func LegacyAdminCredentials() (username, password string) {
|
func LegacyAdminCredentials() (username, password string) {
|
||||||
username, password = "admin", "admin123"
|
return "admin", "admin123"
|
||||||
for _, path := range legacyConfigPaths() {
|
|
||||||
cfg, err := config.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if u := strings.TrimSpace(cfg.Admin.Username); u != "" {
|
|
||||||
username = u
|
|
||||||
}
|
|
||||||
if p := strings.TrimSpace(cfg.Admin.Password); p != "" {
|
|
||||||
password = p
|
|
||||||
}
|
|
||||||
return username, password
|
|
||||||
}
|
|
||||||
return username, password
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-13
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Luminary AI Gateway</title>
|
|
||||||
<script type="module" crossorigin src="/assets/index-DZZ57wgd.js"></script>
|
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CDyLXzvy.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Luminary AI Gateway</title>
|
<title>Luminary AI Gateway</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect width="32" height="32" rx="8" fill="#0f7a62"/>
|
||||||
|
<circle cx="16" cy="16" r="2" fill="#fafafa"/>
|
||||||
|
<path d="M16 8v4.5" stroke="#fafafa" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M16 19.5V24" stroke="#fafafa" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M8 16h4.5" stroke="#fafafa" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M19.5 16H24" stroke="#fafafa" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 512 B |
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="2" fill="currentColor" />
|
||||||
|
<path d="M12 4v4.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M12 15.5V20" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M4 12h4.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M15.5 12H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
withDefaults(defineProps<{ size?: number | string }>(), { size: 20 })
|
||||||
|
</script>
|
||||||
@@ -3,11 +3,7 @@
|
|||||||
<aside class="sidebar custom-scrollbar">
|
<aside class="sidebar custom-scrollbar">
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
<div class="brand-icon">
|
<div class="brand-icon">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<BrandIcon :size="20" />
|
||||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="brand-name">{{ t('common.brand') }}</span>
|
<span class="brand-name">{{ t('common.brand') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,6 +53,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Connection, Document, Key, Lock, Odometer, Setting } from '@element-plus/icons-vue'
|
import { Connection, Document, Key, Lock, Odometer, Setting } from '@element-plus/icons-vue'
|
||||||
import api from '@/api/client'
|
import api from '@/api/client'
|
||||||
|
import BrandIcon from '@/components/BrandIcon.vue'
|
||||||
import LocaleSwitcher from '@/components/LocaleSwitcher.vue'
|
import LocaleSwitcher from '@/components/LocaleSwitcher.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<div class="login-wrap">
|
<div class="login-wrap">
|
||||||
<div class="login-brand-icon">
|
<div class="login-brand-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<BrandIcon :size="24" />
|
||||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<h1 class="page-title">{{ t('common.brand') }}</h1>
|
<h1 class="page-title">{{ t('common.brand') }}</h1>
|
||||||
<p class="page-desc">{{ t('login.subtitle') }}</p>
|
<p class="page-desc">{{ t('login.subtitle') }}</p>
|
||||||
@@ -38,6 +34,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import api from '@/api/client'
|
import api from '@/api/client'
|
||||||
|
import BrandIcon from '@/components/BrandIcon.vue'
|
||||||
import LocaleSwitcher from '@/components/LocaleSwitcher.vue'
|
import LocaleSwitcher from '@/components/LocaleSwitcher.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
Reference in New Issue
Block a user