文件夹挂载逻辑变动

This commit is contained in:
2026-07-30 00:18:06 +08:00
parent 826ce399e7
commit defc9d00d3
5 changed files with 115 additions and 86 deletions
+2 -1
View File
@@ -399,7 +399,8 @@ func getFrpcStatusHandler(c *gin.Context) {
// ========== 日志 Handler ==========
func getFrpcLogHandler(c *gin.Context) {
lines, err := readTailLog("./frpc.log", 200)
// 从 ./data/frpc.log 读取日志
lines, err := readTailLog("./data/frpc.log", 200)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 0,
+1 -46
View File
@@ -17,17 +17,7 @@ import (
var DB *sql.DB
// ============================================================
// 数据库版本常量
// ============================================================
const (
SchemaVersion = "v2" // 当前数据库 Schema 版本
)
// ============================================================
// 数据模型
// ============================================================
const SchemaVersion = "v2"
type GlobalConfig struct {
ID int `json:"id"`
@@ -61,10 +51,6 @@ type User struct {
CreatedAt string `json:"createdAt"`
}
// ============================================================
// 数据库初始化
// ============================================================
func InitDB() error {
// 确保 data 目录存在
if err := os.MkdirAll("./data", 0755); err != nil {
@@ -94,12 +80,7 @@ func InitDB() error {
return nil
}
// ============================================================
// 建表
// ============================================================
func createTables() error {
// 用户表
_, err := DB.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -113,7 +94,6 @@ func createTables() error {
return err
}
// 全局配置表
_, err = DB.Exec(`
CREATE TABLE IF NOT EXISTS global_config (
id INTEGER PRIMARY KEY CHECK (id = 1),
@@ -135,7 +115,6 @@ func createTables() error {
return err
}
// 隧道表
_, err = DB.Exec(`
CREATE TABLE IF NOT EXISTS proxies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -153,7 +132,6 @@ func createTables() error {
return err
}
// 应用配置表
_, err = DB.Exec(`
CREATE TABLE IF NOT EXISTS app_config (
key TEXT PRIMARY KEY,
@@ -165,7 +143,6 @@ func createTables() error {
return err
}
// 初始化默认配置
var count int
DB.QueryRow("SELECT COUNT(*) FROM global_config").Scan(&count)
if count == 0 {
@@ -185,10 +162,6 @@ func createTables() error {
return nil
}
// ============================================================
// 迁移引擎
// ============================================================
func getCurrentSchemaVersion() string {
var version string
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'schema_version'").Scan(&version)
@@ -272,7 +245,6 @@ func runMigrations() error {
log.Printf("📌 当前数据库 Schema: %s, 目标版本: %s", currentVer, targetVer)
if currentVer == targetVer {
// 检查数据库是否包含有效数据
var userCount int
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
if err != nil || userCount == 0 {
@@ -302,7 +274,6 @@ func runMigrations() error {
if currentSchema == nil || schemaVersionsEqual(currentSchema, targetSchema) {
log.Println(" 迁移类型: 轻量复制(Schema 无变更)")
// 验证数据库是否有效
var userCount int
err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
if err != nil || userCount == 0 {
@@ -448,10 +419,6 @@ func buildInsertSQL(oldTable, newTable string, oldDef, newDef *SchemaVersionDef)
), nil
}
// ============================================================
// JWT 密钥管理
// ============================================================
func ensureJwtSecret() error {
var value string
err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'jwt_secret'").Scan(&value)
@@ -484,10 +451,6 @@ func GetJwtSecret() (string, error) {
return secret, nil
}
// ============================================================
// 全局配置 CRUD
// ============================================================
func GetGlobalConfig() (*GlobalConfig, error) {
var cfg GlobalConfig
err := DB.QueryRow(`
@@ -523,10 +486,6 @@ func UpdateGlobalConfig(cfg *GlobalConfig) error {
return err
}
// ============================================================
// 隧道 CRUD
// ============================================================
func GetProxies() ([]Proxy, error) {
rows, err := DB.Query(`
SELECT id, name, type, local_ip, local_port, remote_port, enabled
@@ -589,10 +548,6 @@ func DeleteProxy(id int) error {
return err
}
// ============================================================
// 用户 CRUD
// ============================================================
func GetUserByUsername(username string) (*User, error) {
var u User
err := DB.QueryRow(`
+3 -2
View File
@@ -431,11 +431,12 @@ do_deploy() {
# ----- 启动新容器 -----
print_step "启动 frpc-console 容器..."
# 启动容器(只挂载数据目录)
docker run -d \
--name frpc-console \
--restart=always \
--network host \
-v ${DEPLOY_DIR}:/app \
-v ${DEPLOY_DIR}/data:/app/data \
-e PORT=${PORT} \
-e TZ=Asia/Shanghai \
${IMAGE_NAME}:${IMAGE_TAG}
@@ -449,7 +450,7 @@ do_deploy() {
# ----- 写入版本文件 -----
print_step "写入版本文件..."
echo "$TARGET_VERSION" > "${DEPLOY_DIR}/version.ini"
echo "$TARGET_VERSION" > "${DEPLOY_DIR}/data/version.ini"
print_success "版本写入: ${TARGET_VERSION}"
# ----- 检查 frpc 子进程 -----
+101 -35
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"embed"
"fmt"
"io"
"log"
"os"
"os/exec"
@@ -24,16 +25,12 @@ var FrpcTemplateContent string
var (
cachedFrpcPath string
frpcPathMutex sync.Mutex
frpsPathMutex sync.Mutex
)
// ============================================================
// 获取 frpc 路径
// ============================================================
func getFrpcPath() (string, error) {
frpcPathMutex.Lock()
defer frpcPathMutex.Unlock()
frpsPathMutex.Lock()
defer frpsPathMutex.Unlock()
if cachedFrpcPath != "" {
if _, err := os.Stat(cachedFrpcPath); err == nil {
@@ -61,14 +58,12 @@ func getFrpcPath() (string, error) {
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
}
// 优先级1:本地 ./bin/
localPath := filepath.Join(".", "bin", fileName)
if _, err := os.Stat(localPath); err == nil {
cachedFrpcPath = localPath
return localPath, nil
}
// 优先级2embed 解压
data, err := embeddedFrpc.ReadFile("bin/" + fileName)
if err == nil {
tmpPath := filepath.Join(os.TempDir(), "frpc")
@@ -85,7 +80,6 @@ func getFrpcPath() (string, error) {
}
}
// 优先级3:系统 PATH
path, err := exec.LookPath("frpc")
if err == nil {
cachedFrpcPath = path
@@ -95,10 +89,6 @@ func getFrpcPath() (string, error) {
return "", fmt.Errorf("未找到 frpc 文件")
}
// ============================================================
// 生成 frpc.tomlv2.0 支持 wireProtocol
// ============================================================
func GenerateFrpcConfig() error {
cfg, err := GetGlobalConfig()
if err != nil {
@@ -117,24 +107,21 @@ func GenerateFrpcConfig() error {
}
}
// 构建模板数据
data := struct {
*GlobalConfig
Proxies []Proxy
WireProtocolLine string // v2 协议配置行(为空则不输出)
WireProtocolLine string
}{
GlobalConfig: cfg,
Proxies: activeProxies,
}
// v2.0: 如果启用 v2 协议,生成配置行
if cfg.WireProtocolV2 {
data.WireProtocolLine = `wireProtocol = "v2"`
} else {
data.WireProtocolLine = "" // 不输出,使用默认 v1
data.WireProtocolLine = ""
}
// 读取模板
var tmplContent string
if _, err := os.Stat("frpc.tmpl"); err == nil {
content, readErr := os.ReadFile("frpc.tmpl")
@@ -157,19 +144,20 @@ func GenerateFrpcConfig() error {
return fmt.Errorf("渲染模板失败: %w", err)
}
if err := os.WriteFile("./frpc.toml", buf.Bytes(), 0644); err != nil {
// 所有文件写入 ./data/ 目录
if err := os.MkdirAll("./data", 0755); err != nil {
return fmt.Errorf("创建 data 目录失败: %w", err)
}
if err := os.WriteFile("./data/frpc.toml", buf.Bytes(), 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
return nil
}
// ============================================================
// frpc 进程管理
// ============================================================
func isFrpcRunning() bool {
pidData, err := os.ReadFile("./frpc.pid")
pidData, err := os.ReadFile("./data/frpc.pid")
if err != nil {
return false
}
@@ -200,7 +188,11 @@ func StartFrpc() error {
return fmt.Errorf("获取 frpc 路径失败: %w", err)
}
if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) {
if err := os.MkdirAll("./data", 0755); err != nil {
return fmt.Errorf("创建 data 目录失败: %w", err)
}
if _, err := os.Stat("./data/frpc.toml"); os.IsNotExist(err) {
if err := GenerateFrpcConfig(); err != nil {
return fmt.Errorf("生成配置文件失败: %w", err)
}
@@ -210,13 +202,13 @@ func StartFrpc() error {
return nil
}
os.Remove("./frpc.pid")
os.Remove("./data/frpc.pid")
cmd := exec.Command(frpcPath, "-c", "./frpc.toml")
cmd := exec.Command(frpcPath, "-c", "./data/frpc.toml")
setWindowHide(cmd)
setSysProcAttr(cmd)
logFile, err := os.OpenFile("./frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
logFile, err := os.OpenFile("./data/frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("打开日志文件失败: %w", err)
}
@@ -227,7 +219,7 @@ func StartFrpc() error {
return fmt.Errorf("启动 frpc 失败: %w", err)
}
if err := os.WriteFile("./frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
if err := os.WriteFile("./data/frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil {
return fmt.Errorf("保存 PID 失败: %w", err)
}
@@ -240,11 +232,11 @@ func StopFrpc() error {
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("停止 frpc 失败: %w", err)
}
os.Remove("./frpc.pid")
os.Remove("./data/frpc.pid")
return nil
}
pidData, err := os.ReadFile("./frpc.pid")
pidData, err := os.ReadFile("./data/frpc.pid")
if err != nil {
cmd := exec.Command("pkill", "-f", "frpc")
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
@@ -256,7 +248,7 @@ func StopFrpc() error {
pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData)))
process, err := os.FindProcess(pid)
if err != nil {
os.Remove("./frpc.pid")
os.Remove("./data/frpc.pid")
return nil
}
@@ -264,7 +256,7 @@ func StopFrpc() error {
return fmt.Errorf("杀死进程失败: %w", err)
}
os.Remove("./frpc.pid")
os.Remove("./data/frpc.pid")
return nil
}
@@ -283,7 +275,7 @@ func ReloadFrpc() error {
return fmt.Errorf("获取 frpc 路径失败: %w", err)
}
cmd := exec.Command(frpcPath, "reload", "-c", "./frpc.toml")
cmd := exec.Command(frpcPath, "reload", "-c", "./data/frpc.toml")
_, err = cmd.CombinedOutput()
if err != nil {
log.Printf("⚠️ 热加载失败 (%v),自动降级为重启 frpc", err)
@@ -297,3 +289,77 @@ func ReloadFrpc() error {
}
return nil
}
// readTailLog 读取文件末尾 n 行
func readTailLog(filePath string, n int) ([]string, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, err
}
fileSize := info.Size()
if fileSize == 0 {
return []string{}, nil
}
const chunkSize = 4096
var lines []string
var leftover []byte
offset := fileSize
for len(lines) < n && offset > 0 {
readSize := chunkSize
if offset < int64(chunkSize) {
readSize = int(offset)
}
offset -= int64(readSize)
buf := make([]byte, readSize)
_, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF {
return nil, err
}
data := append(buf, leftover...)
leftover = nil
start := 0
for i := len(data) - 1; i >= 0; i-- {
if data[i] == '\n' {
if i+1 < len(data) {
line := string(data[i+1:])
if line != "" {
lines = append([]string{line}, lines...)
if len(lines) >= n {
break
}
}
}
start = i
}
}
if len(lines) < n && start > 0 {
leftover = data[:start]
}
}
if len(lines) < n && len(leftover) > 0 {
parts := strings.Split(string(leftover), "\n")
for i := len(parts) - 1; i >= 0; i-- {
if parts[i] != "" {
lines = append([]string{parts[i]}, lines...)
if len(lines) >= n {
break
}
}
}
}
return lines, nil
}
+8 -2
View File
@@ -8,8 +8,13 @@ import (
)
func main() {
// 读取 version.ini 显示版本(如果存在
if data, err := os.ReadFile("./version.ini"); err == nil {
// 确保 data 目录存在
if err := os.MkdirAll("./data", 0755); err != nil {
log.Printf("⚠️ 创建 data 目录失败: %v", err)
}
// 读取 version.ini 显示版本
if data, err := os.ReadFile("./data/version.ini"); err == nil {
version := strings.TrimSpace(string(data))
log.Printf("📌 版本: %s", version)
} else {
@@ -43,6 +48,7 @@ func main() {
func startWatchdog() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
if !isFrpcRunning() {
log.Println("⚠️ frpc 进程已停止,自动重启...")