diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..9090bc2 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,41 @@ +# FLVX - Modification Notice + +This project is a derivative work based on **flux-panel** (https://github.com/bqlpfy/flux-panel). + +## Original Project +- **Name**: flux-panel +- **Source**: https://github.com/bqlpfy/flux-panel +- **License**: Apache License 2.0 + +## Modifications +The following major changes and additions have been made in this fork (FLVX): + +### 1. Backend Architecture (Replaced) +- **Removed**: The original `springboot-backend/` (Java/Spring Boot) has been entirely removed. +- **Added**: A new `go-backend/` (Go/SQLite) implementation replaces the original backend. + +### 2. Forwarding Agent (Modified) +- **Modified**: `go-gost/` - Modified forwarding agent wrapper. +- **Modified**: `go-gost/x/` - Modified local fork of the `gost` extensions library. + +### 3. Frontend (Modified) +- **Modified**: `vite-frontend/` - Significant updates to the React/Vite dashboard to compatible with the new Go backend, including UI/UX improvements (HeroUI + Tailwind). + +### 4. Mobile Applications (Removed) +- **Removed**: `android-app/` - Source code for the Android client. +- **Removed**: `ios-app/` - Source code for the iOS client. + +### 5. Infrastructure & Scripts +- **Modified**: `docker-compose-v4.yml`, `docker-compose-v6.yml` (Updated for Go backend). +- **Modified**: `install.sh`, `panel_install.sh` (Updated installation logic). +- **Added**: `AGENTS.md` (Project documentation). + +## License for Modifications +All new files, directories, and modifications introduced in this fork are licensed under the **GNU General Public License v3.0 (GPLv3)**. + +> This program is free software: you can redistribute it and/or modify +> it under the terms of the GNU General Public License as published by +> the Free Software Foundation, either version 3 of the License, or +> (at your option) any later version. + +For the full text of the GPLv3, see . diff --git a/README.md b/README.md index d09cb84..ef768d2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > 📞 **联系我们**: [Telegram群组](https://t.me/flvxpanel) -本项目基于 [go-gost/gost](https://github.com/go-gost/gost) 和 [go-gost/x](https://github.com/go-gost/x) 两个开源库,实现了转发面板。 +本项目来自[flux](https://github.com/bqlpfy/flux-panel)二次开发 。 --- ## 特性 diff --git a/go-backend/internal/store/sqlite/repository.go b/go-backend/internal/store/sqlite/repository.go index 5c43c89..9051f64 100644 --- a/go-backend/internal/store/sqlite/repository.go +++ b/go-backend/internal/store/sqlite/repository.go @@ -1282,6 +1282,8 @@ func migrateSchema(db *sql.DB) error { "allowed_ips": "TEXT DEFAULT ''", }, "node": { + "server_ip_v4": "VARCHAR(100)", + "server_ip_v6": "VARCHAR(100)", "inx": "INTEGER NOT NULL DEFAULT 0", "is_remote": "INTEGER DEFAULT 0", "remote_url": "TEXT", diff --git a/go-backend/tests/contract/migration_contract_test.go b/go-backend/tests/contract/migration_contract_test.go index 76ec67e..5795474 100644 --- a/go-backend/tests/contract/migration_contract_test.go +++ b/go-backend/tests/contract/migration_contract_test.go @@ -2,6 +2,7 @@ package contract_test import ( "bytes" + "database/sql" "encoding/json" "io" "net/http" @@ -17,6 +18,8 @@ import ( "go-backend/internal/http/handler" "go-backend/internal/http/response" "go-backend/internal/store/sqlite" + + _ "modernc.org/sqlite" ) func TestCaptchaVerifyLoginContract(t *testing.T) { @@ -213,3 +216,117 @@ func setupContractRouter(t *testing.T, jwtSecret string) (http.Handler, *sqlite. h := handler.New(repo, jwtSecret) return httpserver.NewRouter(h, jwtSecret), repo } + +func TestOpenMigratesLegacyNodeDualStackColumns(t *testing.T) { + dbPath := filepath.Join(t.TempDir(), "legacy-2.0.7-beta.db") + legacyDB, err := sql.Open("sqlite", dbPath) + if err != nil { + t.Fatalf("open legacy sqlite: %v", err) + } + + t.Cleanup(func() { + _ = legacyDB.Close() + }) + + if _, err := legacyDB.Exec(` + CREATE TABLE IF NOT EXISTS node ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL, + secret VARCHAR(100) NOT NULL, + server_ip VARCHAR(100) NOT NULL, + port TEXT NOT NULL, + interface_name VARCHAR(200), + version VARCHAR(100), + http INTEGER NOT NULL DEFAULT 0, + tls INTEGER NOT NULL DEFAULT 0, + socks INTEGER NOT NULL DEFAULT 0, + created_time INTEGER NOT NULL, + updated_time INTEGER, + status INTEGER NOT NULL, + tcp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]', + udp_listen_addr VARCHAR(100) NOT NULL DEFAULT '[::]' + ) + `); err != nil { + t.Fatalf("create legacy node table: %v", err) + } + + if _, err := legacyDB.Exec(` + CREATE TABLE IF NOT EXISTS tunnel ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL, + traffic_ratio REAL NOT NULL DEFAULT 1.0, + type INTEGER NOT NULL, + protocol VARCHAR(10) NOT NULL DEFAULT 'tls', + flow INTEGER NOT NULL, + created_time INTEGER NOT NULL, + updated_time INTEGER NOT NULL, + status INTEGER NOT NULL, + in_ip TEXT + ) + `); err != nil { + t.Fatalf("create legacy tunnel table: %v", err) + } + + now := time.Now().UnixMilli() + if _, err := legacyDB.Exec(` + INSERT INTO node(name, secret, server_ip, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, "legacy-node", "legacy-secret", "10.10.0.1", "10000-10010", "eth0", "v-old", 1, 1, 1, now, now, 1, "[::]", "[::]"); err != nil { + t.Fatalf("seed legacy node row: %v", err) + } + + repo, err := sqlite.Open(dbPath) + if err != nil { + t.Fatalf("open migrated sqlite: %v", err) + } + t.Cleanup(func() { + _ = repo.Close() + }) + + nodes, err := repo.ListNodes() + if err != nil { + t.Fatalf("list nodes after migration: %v", err) + } + if len(nodes) != 1 { + t.Fatalf("expected 1 node after migration, got %d", len(nodes)) + } + + columns := readTableColumns(t, repo.DB(), "node") + + for _, required := range []string{"server_ip_v4", "server_ip_v6", "inx"} { + if !columns[required] { + t.Fatalf("expected node column %q to exist after migration", required) + } + } + + tunnelColumns := readTableColumns(t, repo.DB(), "tunnel") + if !tunnelColumns["inx"] { + t.Fatalf("expected tunnel column %q to exist after migration", "inx") + } +} + +func readTableColumns(t *testing.T, db *sql.DB, table string) map[string]bool { + t.Helper() + + rows, err := db.Query("PRAGMA table_info(" + table + ")") + if err != nil { + t.Fatalf("inspect %s columns: %v", table, err) + } + defer rows.Close() + + columns := map[string]bool{} + for rows.Next() { + var cid, notNull, pk int + var name, typ string + var defaultValue sql.NullString + if err := rows.Scan(&cid, &name, &typ, ¬Null, &defaultValue, &pk); err != nil { + t.Fatalf("scan %s pragma row: %v", table, err) + } + columns[name] = true + } + if err := rows.Err(); err != nil { + t.Fatalf("iterate %s pragma rows: %v", table, err) + } + + return columns +}