Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 530cfed686 | |||
| c7c10c7c17 | |||
| ee54689fe9 | |||
| f1b01ec18f |
+5
-4
@@ -3,7 +3,8 @@ FROM golang:1.24-alpine AS builder
|
||||
|
||||
ENV GOTOOLCHAIN=auto
|
||||
|
||||
RUN apk add --no-cache git
|
||||
# Install build dependencies for SQLite3
|
||||
RUN apk add --no-cache git gcc musl-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -15,10 +16,10 @@ RUN go mod download
|
||||
COPY server/ ./
|
||||
COPY agent/ ../agent/
|
||||
|
||||
# Build server
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /nuyue-server ./cmd/server
|
||||
# Build server with CGO enabled for SQLite3
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w -linkmode external -extldflags '-static'" -o /nuyue-server ./cmd/server
|
||||
|
||||
# Build agent
|
||||
# Build agent (no CGO needed)
|
||||
RUN cd ../agent && CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /nuyue-agent ./cmd/agent
|
||||
|
||||
# Runtime stage
|
||||
|
||||
@@ -201,9 +201,8 @@ func setupRouter(app *Application) *gin.Engine {
|
||||
})
|
||||
})
|
||||
|
||||
// API v1
|
||||
// 安装向导路由(未安装时)
|
||||
if !app.Config.IsInstalled() {
|
||||
// 安装向导(未安装时可用)
|
||||
installRepo := install.NewRepository(app.DB)
|
||||
installSvc := install.NewService(installRepo, "./config.yaml")
|
||||
installHandler := install.NewHandler(installSvc)
|
||||
@@ -215,17 +214,5 @@ func setupRouter(app *Application) *gin.Engine {
|
||||
// TODO: 添加其他模块路由
|
||||
}
|
||||
|
||||
// 安装页面
|
||||
r.GET("/install", func(c *gin.Context) {
|
||||
if app.Config.IsInstalled() {
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
return
|
||||
}
|
||||
// TODO: 返回前端安装页面
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "安装向导页面(前端待实现)",
|
||||
})
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,14 +242,106 @@ func (h *Handler) Complete(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Router /install [get]
|
||||
func (h *Handler) InstallPage(c *gin.Context) {
|
||||
// 检查是否已安装
|
||||
if installed, _ := h.svc.IsInstalled(c.Request.Context()); installed {
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回安装页面(前端静态页面)
|
||||
c.HTML(http.StatusOK, "install.html", gin.H{
|
||||
"title": "怒月 - 安装向导",
|
||||
})
|
||||
// 返回安装页面(简单 HTML,后续替换为前端)
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(http.StatusOK, `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>怒月 - 安装向导</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #0a0a0a; color: #e0e0e0; }
|
||||
h1 { color: #fff; }
|
||||
.step { margin: 20px 0; padding: 20px; background: #1a1a1a; border-radius: 8px; }
|
||||
input, select { width: 100%; padding: 10px; margin: 10px 0; background: #2a2a2a; border: 1px solid #3a3a3a; color: #fff; border-radius: 4px; box-sizing: border-box; }
|
||||
button { padding: 12px 24px; background: #4a9eff; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; }
|
||||
button:hover { background: #3a8eef; }
|
||||
.success { color: #4ade80; }
|
||||
.error { color: #f87171; }
|
||||
#result { margin-top: 20px; padding: 15px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🌙 怒月 - 安装向导</h1>
|
||||
<div class="step">
|
||||
<h3>步骤 1: 数据库配置</h3>
|
||||
<label>数据库类型:</label>
|
||||
<select id="dbType">
|
||||
<option value="sqlite">SQLite (推荐)</option>
|
||||
<option value="postgres">PostgreSQL</option>
|
||||
</select>
|
||||
<div id="sqliteConfig">
|
||||
<label>数据库路径:</label>
|
||||
<input type="text" id="dbPath" value="/data/nuyue.db">
|
||||
</div>
|
||||
<button onclick="testDB()">测试连接</button>
|
||||
<span id="dbResult"></span>
|
||||
</div>
|
||||
<div class="step">
|
||||
<h3>步骤 2: Redis 配置 (可选)</h3>
|
||||
<label>启用 Redis:</label>
|
||||
<select id="redisEnabled">
|
||||
<option value="false">不启用</option>
|
||||
<option value="true">启用</option>
|
||||
</select>
|
||||
<div id="redisConfig" style="display:none;">
|
||||
<label>Redis 地址:</label>
|
||||
<input type="text" id="redisHost" value="redis:6379">
|
||||
</div>
|
||||
<button onclick="testRedis()">测试连接</button>
|
||||
<span id="redisResult"></span>
|
||||
</div>
|
||||
<div class="step">
|
||||
<h3>步骤 3: 管理员账户</h3>
|
||||
<label>用户名:</label>
|
||||
<input type="text" id="adminUser" value="admin">
|
||||
<label>密码:</label>
|
||||
<input type="password" id="adminPass" value="">
|
||||
<label>邮箱:</label>
|
||||
<input type="email" id="adminEmail" value="">
|
||||
</div>
|
||||
<div class="step">
|
||||
<button onclick="completeInstall()">完成安装</button>
|
||||
</div>
|
||||
<div id="result"></div>
|
||||
<script>
|
||||
document.getElementById('redisEnabled').onchange = function() {
|
||||
document.getElementById('redisConfig').style.display = this.value === 'true' ? 'block' : 'none';
|
||||
};
|
||||
async function testDB() {
|
||||
const res = await fetch('/api/v1/install/test-db', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({type: document.getElementById('dbType').value, path: document.getElementById('dbPath').value})
|
||||
});
|
||||
const data = await res.json();
|
||||
document.getElementById('dbResult').textContent = data.code === 0 ? '✓ 连接成功' : '✗ ' + data.message;
|
||||
document.getElementById('dbResult').className = data.code === 0 ? 'success' : 'error';
|
||||
}
|
||||
async function testRedis() {
|
||||
const res = await fetch('/api/v1/install/test-redis', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({enabled: document.getElementById('redisEnabled').value === 'true', host: document.getElementById('redisHost').value})
|
||||
});
|
||||
const data = await res.json();
|
||||
document.getElementById('redisResult').textContent = data.code === 0 ? '✓ 连接成功' : '✗ ' + data.message;
|
||||
document.getElementById('redisResult').className = data.code === 0 ? 'success' : 'error';
|
||||
}
|
||||
async function completeInstall() {
|
||||
const res = await fetch('/api/v1/install/complete', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
database: {type: document.getElementById('dbType').value, path: document.getElementById('dbPath').value},
|
||||
redis: {enabled: document.getElementById('redisEnabled').value === 'true', host: document.getElementById('redisHost').value},
|
||||
admin: {username: document.getElementById('adminUser').value, password: document.getElementById('adminPass').value, email: document.getElementById('adminEmail').value}
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
document.getElementById('result').innerHTML = data.code === 0 ? '<span class="success">✓ 安装完成!<a href="/login">点击登录</a></span>' : '<span class="error">✗ ' + data.message + '</span>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`)
|
||||
}
|
||||
|
||||
@@ -154,8 +154,9 @@ func (r *installRepository) IsInstalled(ctx context.Context) (bool, error) {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return false, nil
|
||||
}
|
||||
// 表不存在或其他错误,返回 false(未安装状态)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return value == "true", nil
|
||||
|
||||
Reference in New Issue
Block a user