chore: 初始提交 Fonu 1.0.2

- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听)
- 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP)
- 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点)
- 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
This commit is contained in:
2026-09-23 09:40:59 +08:00
commit 278f8267b7
333 changed files with 53562 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
package db
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
_ "modernc.org/sqlite"
)
func Open(path string) (*sql.DB, error) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return nil, err
}
dsn := fmt.Sprintf("file:%s?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)", path)
conn, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
conn.SetMaxOpenConns(1)
return conn, nil
}
func Migrate(ctx context.Context, conn *sql.DB, migrationsDir string) error {
if err := ensureMigrationsTable(ctx, conn); err != nil {
return err
}
entries, err := os.ReadDir(migrationsDir)
if err != nil {
return err
}
var files []string
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".sql") {
continue
}
files = append(files, e.Name())
}
sort.Strings(files)
for _, name := range files {
version, err := parseVersion(name)
if err != nil {
return err
}
applied, err := isApplied(ctx, conn, version)
if err != nil {
return err
}
if applied {
continue
}
content, err := os.ReadFile(filepath.Join(migrationsDir, name))
if err != nil {
return err
}
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, string(content)); err != nil {
_ = tx.Rollback()
return fmt.Errorf("migration %s failed: %w", name, err)
}
if _, err := tx.ExecContext(ctx, `INSERT INTO schema_migrations(version) VALUES (?)`, version); err != nil {
_ = tx.Rollback()
return fmt.Errorf("record migration %s failed: %w", name, err)
}
if err := tx.Commit(); err != nil {
return err
}
}
return nil
}
func ensureMigrationsTable(ctx context.Context, conn *sql.DB) error {
_, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`)
return err
}
func parseVersion(name string) (int, error) {
parts := strings.SplitN(name, "_", 2)
if len(parts) == 0 {
return 0, fmt.Errorf("invalid migration filename: %s", name)
}
var version int
if _, err := fmt.Sscanf(parts[0], "%d", &version); err != nil {
return 0, fmt.Errorf("invalid migration version in %s: %w", name, err)
}
return version, nil
}
func isApplied(ctx context.Context, conn *sql.DB, version int) (bool, error) {
var count int
err := conn.QueryRowContext(ctx, `SELECT COUNT(1) FROM schema_migrations WHERE version = ?`, version).Scan(&count)
return count > 0, err
}
+24
View File
@@ -0,0 +1,24 @@
package db
import (
"context"
"path/filepath"
"testing"
)
func TestMigrate(t *testing.T) {
dir := t.TempDir()
conn, err := Open(filepath.Join(dir, "fonu.db"))
if err != nil {
t.Fatalf("open db: %v", err)
}
defer conn.Close()
migrationsDir := filepath.Join("..", "..", "migrations")
if err := Migrate(context.Background(), conn, migrationsDir); err != nil {
t.Fatalf("migrate failed: %v", err)
}
if err := Migrate(context.Background(), conn, migrationsDir); err != nil {
t.Fatalf("second migrate failed: %v", err)
}
}