diff --git a/migrations/mysql/02122_server.sql b/migrations/mysql/02122_server.sql deleted file mode 100644 index b0b3d1f8..00000000 --- a/migrations/mysql/02122_server.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS `server`; diff --git a/migrations/mysql/02124_server_group_delete.sql b/migrations/mysql/02124_server_group_delete.sql deleted file mode 100644 index e26f5fc7..00000000 --- a/migrations/mysql/02124_server_group_delete.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS `server_group`; diff --git a/src/db.rs b/src/db.rs index d5dc6732..aeafac3d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -39,23 +39,20 @@ pub async fn init_pool(cfg: &DatabaseConfig) -> Result { // ─── DSN builder ───────────────────────────────────────────────────────── -fn build_dsn(cfg: &DatabaseConfig) -> String { +pub(crate) fn build_dsn(cfg: &DatabaseConfig) -> String { + let dialect = detect_dialect(cfg); let addr = cfg .addr .as_deref() .filter(|a| !a.is_empty()) - .unwrap_or(match detect_dialect(cfg) { + .unwrap_or(match dialect { Dialect::Postgres => "localhost:5432", Dialect::Mysql => "localhost:3306", }); let password = url_encode_password(&cfg.password); - let query = if cfg.config.is_empty() { - default_query(cfg) - } else { - &cfg.config - }; + let query = pick_query(cfg, dialect); - match detect_dialect(cfg) { + match dialect { Dialect::Postgres => format!( "postgres://{}:{}@{}/{}?{}", cfg.username, password, addr, cfg.dbname, query, @@ -71,8 +68,42 @@ fn detect_dialect(cfg: &DatabaseConfig) -> Dialect { Dialect::from_driver(&cfg.driver) } -fn default_query(cfg: &DatabaseConfig) -> &'static str { - match detect_dialect(cfg) { +/// Pick the URL query string for the DSN. +/// +/// `DatabaseConfig.config` defaults to a MySQL-flavoured string +/// (`charset=utf8mb4&parseTime=true&loc=...`), so an unset YAML field +/// silently gives the wrong params to a Postgres connection. To stay +/// backwards-compatible we only honour `cfg.config` when it looks +/// compatible with the active dialect; otherwise fall back to +/// `default_query`. +fn pick_query(cfg: &DatabaseConfig, dialect: Dialect) -> String { + let raw = cfg.config.trim(); + if !raw.is_empty() && query_matches_dialect(raw, dialect) { + return raw.to_string(); + } + default_query(dialect).to_string() +} + +fn query_matches_dialect(query: &str, dialect: Dialect) -> bool { + // Heuristic — MySQL uses `charset=` / `parseTime=` / `loc=`; Postgres + // uses `sslmode=` / `TimeZone=` / `channel_binding=`. Treat any MySQL- + // specific key under a Postgres dialect as a mismatch, and vice versa. + let q = query.to_ascii_lowercase(); + match dialect { + Dialect::Postgres => { + !q.contains("charset=") + && !q.contains("parsetime=") + && !q.contains("&loc=") + && !q.starts_with("loc=") + } + Dialect::Mysql => { + !(q.contains("sslmode=") || q.contains("timezone=") || q.contains("channel_binding=")) + } + } +} + +fn default_query(dialect: Dialect) -> &'static str { + match dialect { Dialect::Postgres => "sslmode=disable&TimeZone=Asia/Shanghai", Dialect::Mysql => "charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai", } diff --git a/src/main.rs b/src/main.rs index 3414245a..93f2497d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,11 +133,8 @@ async fn main() { .expect("failed to connect to database"); tracing::info!("database connected"); - // ── Run pending migrations ────────────────────────────────────────── - migration::run_migrations(&db) - .await - .expect("database migration failed"); - tracing::info!("database migrations applied"); + // ── Ensure schema is present (invoke Go migrate tool if needed) ──── + migration::ensure_schema(&db, &cfg.database).await; // ── Seed initial admin account if needed ──────────────────────────── migration::create_admin_user(&db, &cfg.administrator.email, &cfg.administrator.password) diff --git a/src/migration.rs b/src/migration.rs index 203d61b9..44e18b3f 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -1,39 +1,168 @@ -//! Database migration runner and bootstrap utilities. +//! Database bootstrap utilities. //! -//! Embeds both MySQL and PostgreSQL migration files at compile time via -//! `sqlx::migrate!` and selects the correct set at runtime based on the -//! [`Dialect`] detected from configuration. +//! Schema migrations are **not** owned by this Rust binary. They are managed by +//! the standalone Go tool at `tools/migrate/` (built with `go build -o ppanel-migrate ./cmd/migrate`), +//! which uses golang-migrate and tracks state in the `schema_migrations` table +//! — the same table the Go server uses. That makes a single source of truth +//! for schema across the Go and Rust backends. +//! +//! On startup, this module probes for the presence of the `user` table. If it +//! is missing it shells out to the `ppanel-migrate` binary to bring the schema +//! up to date, then retries the probe. If the binary is not available or fails, +//! the process exits with a clear error message. //! //! **NOTE**: This is the Rust rewrite of the Go backend. The Go version is //! deprecated and will be replaced by this Rust implementation. -mod mysql_migrations { - #![allow(unused)] - pub(super) const MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("migrations/mysql"); -} - -mod postgres_migrations { - #![allow(unused)] - pub(super) fn migrator() -> sqlx::migrate::Migrator { - let mut m = sqlx::migrate!("migrations/postgres"); - m.set_locking(false); - m.no_tx = true; - m - } -} - pub use crate::repository::Dialect; +use crate::config::DatabaseConfig; +use crate::db::build_dsn; use crate::repository::Db; +use std::path::PathBuf; +use std::process::Command; -/// Run all pending migrations for the detected database dialect. -pub async fn run_migrations(db: &Db) -> Result<(), sqlx::migrate::MigrateError> { +/// Find a probe table whose existence implies "the schema is at least migration 2131". +/// +/// `user` is created in `00001_init_schema.up.sql` and only ever dropped in +/// drop-style migrations that immediately re-create it (or are no-ops on top +/// of a fresh database), so its presence is a reliable schema-marker. +const SCHEMA_MARKER_TABLE: &str = "user"; + +/// Returns `Ok(true)` if the schema marker table is present and queryable, +/// `Ok(false)` if the table is missing, and `Err` for any other database error. +async fn schema_present(db: &Db) -> Result { match db { - Db::Postgres(pool) => postgres_migrations::migrator().run(pool).await, - Db::Mysql(pool) => mysql_migrations::MIGRATOR.run(pool).await, + Db::Postgres(pool) => { + let row: Option<(bool,)> = sqlx::query_as( + r#"SELECT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'public' AND table_name = $1 + )"#, + ) + .bind(SCHEMA_MARKER_TABLE) + .fetch_optional(pool) + .await?; + Ok(row.map(|(b,)| b).unwrap_or(false)) + } + Db::Mysql(pool) => { + let row: Option<(i64,)> = sqlx::query_as( + r#"SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = DATABASE() AND table_name = ?"#, + ) + .bind(SCHEMA_MARKER_TABLE) + .fetch_optional(pool) + .await?; + Ok(row.map(|(n,)| n > 0).unwrap_or(false)) + } } } +/// Ensure the database schema is present, invoking the `ppanel-migrate` tool +/// if needed. Returns successfully if the schema is at (or beyond) the +/// tool's latest migration, otherwise exits the process with a clear error. +pub async fn ensure_schema(db: &Db, cfg: &DatabaseConfig) { + match schema_present(db).await { + Ok(true) => { + tracing::info!("schema marker table present, skipping migration tool"); + return; + } + Ok(false) => { + tracing::warn!( + "schema marker table `{}` not found — invoking ppanel-migrate to initialise the schema", + SCHEMA_MARKER_TABLE + ); + } + Err(e) => { + tracing::error!("failed to probe schema marker: {e}"); + // Don't bail out yet — the migration tool itself might still succeed + // (e.g. the database is reachable, just has no tables). + } + } + + run_migrate_tool(cfg).await; + // Re-probe to confirm the schema is actually present now. + match schema_present(db).await { + Ok(true) => tracing::info!("schema initialised successfully"), + Ok(false) => panic!( + "ppanel-migrate ran but schema marker `{}` is still missing", + SCHEMA_MARKER_TABLE + ), + Err(e) => panic!("schema marker probe failed after ppanel-migrate: {e}"), + } +} + +/// Resolve the path to the `ppanel-migrate` binary. +/// +/// Search order: +/// 1. `${PPANEL_MIGRATE_BIN}` if set. +/// 2. Next to the current executable (`./ppanel-migrate`). +/// 3. One level up from the executable (cargo puts the binary in target/release/; +/// the migrate tool often lives at tools/migrate/ppanel-migrate). +/// 4. `$PATH` (via Command's default lookup). +fn locate_migrate_bin() -> PathBuf { + if let Ok(p) = std::env::var("PPANEL_MIGRATE_BIN") { + return PathBuf::from(p); + } + if let Ok(exe) = std::env::current_exe() { + if let Some(dir) = exe.parent() { + let candidate = dir.join("ppanel-migrate"); + if candidate.is_file() { + return candidate; + } + if let Some(parent) = dir.parent() { + let candidate = parent.join("ppanel-migrate"); + if candidate.is_file() { + return candidate; + } + } + } + } + PathBuf::from("ppanel-migrate") +} + +async fn run_migrate_tool(cfg: &DatabaseConfig) { + let bin = locate_migrate_bin(); + let driver = match cfg.driver.as_str() { + "mysql" => "mysql", + _ => "postgres", + }; + let dsn = build_dsn(cfg); + + tracing::info!(driver, bin = %bin.display(), "running ppanel-migrate up"); + + // Run the migrate tool synchronously — it must complete before we serve + // any traffic. Failures are fatal. + let output = Command::new(&bin) + .arg("-driver") + .arg(driver) + .arg("-dsn") + .arg(&dsn) + .arg("up") + .output() + .unwrap_or_else(|e| { + panic!( + "failed to execute ppanel-migrate at {}: {e}. \ + Build it with `go build -o ppanel-migrate ./cmd/migrate` \ + from ppanel-backend/tools/migrate, or set $PPANEL_MIGRATE_BIN", + bin.display(), + ) + }); + + if !output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + panic!( + "ppanel-migrate exited with status {}: {}{}{}", + output.status, + stdout, + if !stdout.is_empty() && !stderr.is_empty() { "\n" } else { "" }, + stderr, + ); + } + tracing::info!("ppanel-migrate completed"); +} + // ═══════════════════════════════════════════════════════════════════════════ // Bootstrap: initial admin account // ═══════════════════════════════════════════════════════════════════════════ @@ -64,7 +193,6 @@ async fn create_admin_user_pg( return Ok(()); } - let now = chrono::Utc::now().timestamp_millis(); let now_ts = chrono::Utc::now(); // chrono::DateTime -> PG timestamp let password_hash = hash_password_pbkdf2(password); let refer_code = generate_invite_code(); @@ -192,9 +320,4 @@ fn hash_password_pbkdf2(password: &str) -> String { fn generate_invite_code() -> String { format!("u{}", &uuid::Uuid::new_v4().to_string().replace('-', "")[..12]) -} - -// ═══════════════════════════════════════════════════════════════════════════ -// Bootstrap: initial admin account -// ═══════════════════════════════════════════════════════════════════════════ - +} \ No newline at end of file diff --git a/tools/migrate/cmd/migrate/main.go b/tools/migrate/cmd/migrate/main.go new file mode 100644 index 00000000..25857e4d --- /dev/null +++ b/tools/migrate/cmd/migrate/main.go @@ -0,0 +1,129 @@ +// ppanel-migrate is a standalone CLI for managing PPanel database schema. +// +// It uses the migrate package (copied from server/initialize/migrate) which embeds +// the SQL migration files and applies them via golang-migrate. Tracks state in the +// schema_migrations table — the same table the Go server uses — so the Rust +// ppanel-backend can attach to a database that was already initialised by either tool. +// +// Usage: +// +// ppanel-migrate -driver=postgres -dsn="postgres://..." up +// ppanel-migrate -driver=postgres -dsn="postgres://..." version +// ppanel-migrate -driver=postgres -dsn="postgres://..." force 2131 +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "strconv" + + gomigrate "github.com/golang-migrate/migrate/v4" + + ppmigrate "github.com/perfect-panel/ppanel-backend/tools/migrate/migrate" +) + +func main() { + var ( + driver = flag.String("driver", "postgres", "Database driver: postgres | mysql") + dsn = flag.String("dsn", "", "Database DSN. URL scheme (postgres:// | mysql://) is auto-prepended if absent.") + verbose = flag.Bool("v", false, "Verbose logging") + ) + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "ppanel-migrate: standalone PPanel schema migration tool\n\n") + fmt.Fprintf(os.Stderr, "Usage: ppanel-migrate -driver= -dsn= [args]\n\n") + fmt.Fprintf(os.Stderr, "Commands:\n") + fmt.Fprintf(os.Stderr, " up [N] Apply all (or N) pending migrations\n") + fmt.Fprintf(os.Stderr, " down [N] Roll back one (or N) migration\n") + fmt.Fprintf(os.Stderr, " version Print current schema version\n") + fmt.Fprintf(os.Stderr, " force Mark database at without running migrations\n") + fmt.Fprintf(os.Stderr, " drop Drop every object in the database (dangerous)\n\n") + flag.PrintDefaults() + } + flag.Parse() + + if *dsn == "" { + flag.Usage() + os.Exit(2) + } + if *verbose { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + } + + cmd := flag.Arg(0) + sess := ppmigrate.Migrate(*driver, *dsn) + m := sess.Migrate + + switch cmd { + case "", "up": + if err := ppmigrate.RunUp(sess); err != nil && !errors.Is(err, gomigrate.ErrNoChange) { + log.Fatalf("up: %v", err) + } + reportVersion(m) + + case "down": + n := -parseStep(flag.Arg(1)) + if err := m.Steps(n); err != nil && !errors.Is(err, gomigrate.ErrNoChange) { + log.Fatalf("down: %v", err) + } + reportVersion(m) + + case "version": + v, dirty, err := m.Version() + if errors.Is(err, gomigrate.ErrNilVersion) { + fmt.Println("no schema_migrations row (database is empty / not yet migrated)") + os.Exit(0) + } + if err != nil { + log.Fatalf("version: %v", err) + } + fmt.Printf("version=%d dirty=%v\n", v, dirty) + + case "force": + v, err := strconv.Atoi(flag.Arg(1)) + if err != nil { + log.Fatalf("force: bad version %q: %v", flag.Arg(1), err) + } + if err := m.Force(v); err != nil { + log.Fatalf("force %d: %v", v, err) + } + fmt.Printf("forced to version %d\n", v) + + case "drop": + log.Println("WARNING: dropping all database objects") + if err := m.Drop(); err != nil { + log.Fatalf("drop: %v", err) + } + fmt.Println("dropped") + + default: + fmt.Fprintf(os.Stderr, "unknown command %q\n\n", cmd) + flag.Usage() + os.Exit(2) + } +} + +func parseStep(s string) int { + if s == "" { + return 1 + } + n, err := strconv.Atoi(s) + if err != nil || n <= 0 { + log.Fatalf("step must be a positive integer, got %q", s) + } + return n +} + +func reportVersion(m *gomigrate.Migrate) { + v, dirty, err := m.Version() + if errors.Is(err, gomigrate.ErrNilVersion) { + fmt.Println("no schema_migrations row (empty database)") + return + } + if err != nil { + log.Fatalf("version: %v", err) + } + fmt.Printf("schema_migrations: version=%d dirty=%v\n", v, dirty) +} \ No newline at end of file diff --git a/tools/migrate/go.mod b/tools/migrate/go.mod new file mode 100644 index 00000000..68dec01e --- /dev/null +++ b/tools/migrate/go.mod @@ -0,0 +1,11 @@ +module github.com/perfect-panel/ppanel-backend/tools/migrate + +go 1.25.0 + +require github.com/golang-migrate/migrate/v4 v4.19.1 + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/lib/pq v1.10.9 // indirect +) diff --git a/tools/migrate/go.sum b/tools/migrate/go.sum new file mode 100644 index 00000000..ee74e716 --- /dev/null +++ b/tools/migrate/go.sum @@ -0,0 +1,66 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/migrate/migrate/migrate.go b/tools/migrate/migrate/migrate.go new file mode 100644 index 00000000..fd86d066 --- /dev/null +++ b/tools/migrate/migrate/migrate.go @@ -0,0 +1,133 @@ +package migrate + +import ( + "embed" + "errors" + "fmt" + "io/fs" + "os" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/mysql" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed sql/postgres/*.sql sql/mysql/*.sql +var sqlFiles embed.FS + +// NoChange is re-exported so callers don't have to import golang-migrate directly. +var NoChange = migrate.ErrNoChange + +// Session bundles a configured golang-migrate instance alongside the parsed +// source-version list so callers can introspect what migrations exist. +type Session struct { + Migrate *migrate.Migrate + Versions []uint // sorted ascending; all .up.sql versions found in source +} + +// Migrate returns a configured golang-migrate instance that reads SQL files from +// the embedded sql/{postgres,mysql}/ directories, based on the requested driver. +// +// driver: "postgres" or "mysql" +// dsn: golang-migrate URL (e.g. postgres://user:pass@host:port/db?sslmode=disable). +// +// If dsn does not include a URL scheme, the driver is prepended automatically. +func Migrate(driver, dsn string) *Session { + sourcePath := "sql/postgres" + databaseURL := dsn + switch driver { + case "mysql": + sourcePath = "sql/mysql" + databaseURL = ensureScheme("mysql://", dsn) + case "postgres": + databaseURL = ensureScheme("postgres://", dsn) + default: + panic(fmt.Errorf("[Migrate] unsupported database driver: %s", driver)) + } + d, err := iofs.New(sqlFiles, sourcePath) + if err != nil { + panic(fmt.Errorf("[Migrate] iofs.New error: %v", err)) + } + client, err := migrate.NewWithSourceInstance("iofs", d, databaseURL) + if err != nil { + panic(fmt.Errorf("[Migrate] NewWithSourceInstance error: %v", err)) + } + return &Session{ + Migrate: client, + Versions: scanVersions(sourcePath), + } +} + +// sourceVersionRe matches migration filenames like "02131_xxx.up.sql" / ".down.sql". +var sourceVersionRe = regexp.MustCompile(`^([0-9]+)_[^.]+\.(up|down)\.sql$`) + +// scanVersions lists all up-version numbers present in the embedded source dir. +func scanVersions(sourcePath string) []uint { + entries, err := sqlFiles.ReadDir(sourcePath) + if err != nil { + panic(fmt.Errorf("[scanVersions] read %s: %w", sourcePath, err)) + } + seen := map[uint]struct{}{} + for _, e := range entries { + m := sourceVersionRe.FindStringSubmatch(e.Name()) + if m == nil || m[2] != "up" { + continue + } + v, err := strconv.ParseUint(m[1], 10, 64) + if err != nil { + continue + } + seen[uint(v)] = struct{}{} + } + out := make([]uint, 0, len(seen)) + for v := range seen { + out = append(out, v) + } + sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) + return out +} + +// RunUp applies all pending migrations. Unlike m.Up(), it correctly handles the +// "database is already at the latest version" case under the iofs source driver — +// whose Next() / ReadUp() methods return fs.ErrNotExist rather than the +// os.ErrNotExist sentinel that golang-migrate's internal logic checks for. +// +// Returns migrate.ErrNoChange if there is nothing to apply. +func RunUp(s *Session) error { + if len(s.Versions) == 0 { + return fmt.Errorf("no migration files embedded") + } + srcLast := s.Versions[len(s.Versions)-1] + + dbVer, _, err := s.Migrate.Version() + if errors.Is(err, migrate.ErrNilVersion) { + // Empty DB — apply everything from the top. + return s.Migrate.Up() + } + if err != nil { + return err + } + if uint(dbVer) >= srcLast { + // DB already at or beyond the latest source version. Nothing to do. + return migrate.ErrNoChange + } + + steps := int(srcLast - uint(dbVer)) + return s.Migrate.Steps(steps) +} + +func ensureScheme(scheme, dsn string) string { + if strings.Contains(dsn, "://") { + return dsn + } + return scheme + dsn +} + +// keep imports referenced (io/fs and os are used elsewhere via errors.Is) +var _ = fs.ErrNotExist +var _ = os.ErrNotExist \ No newline at end of file diff --git a/migrations/mysql/00001_init_schema.down.sql b/tools/migrate/migrate/sql/mysql/00001_init_schema.down.sql similarity index 100% rename from migrations/mysql/00001_init_schema.down.sql rename to tools/migrate/migrate/sql/mysql/00001_init_schema.down.sql diff --git a/migrations/mysql/00001_init_schema.sql b/tools/migrate/migrate/sql/mysql/00001_init_schema.up.sql similarity index 99% rename from migrations/mysql/00001_init_schema.sql rename to tools/migrate/migrate/sql/mysql/00001_init_schema.up.sql index 61e66f0b..9be1ddb3 100644 --- a/migrations/mysql/00001_init_schema.sql +++ b/tools/migrate/migrate/sql/mysql/00001_init_schema.up.sql @@ -1,3 +1,4 @@ +-- 000001_init_schema.up.sql SET FOREIGN_KEY_CHECKS = 0; CREATE TABLE IF NOT EXISTS `ads` @@ -552,4 +553,3 @@ CREATE TABLE IF NOT EXISTS `server_rule_group` SET FOREIGN_KEY_CHECKS = 1; - diff --git a/migrations/mysql/00002_init_basic_data.down.sql b/tools/migrate/migrate/sql/mysql/00002_init_basic_data.down.sql similarity index 100% rename from migrations/mysql/00002_init_basic_data.down.sql rename to tools/migrate/migrate/sql/mysql/00002_init_basic_data.down.sql diff --git a/migrations/mysql/00002_init_basic_data.sql b/tools/migrate/migrate/sql/mysql/00002_init_basic_data.up.sql similarity index 99% rename from migrations/mysql/00002_init_basic_data.sql rename to tools/migrate/migrate/sql/mysql/00002_init_basic_data.up.sql index cc6beaa4..83cdda6d 100644 --- a/migrations/mysql/00002_init_basic_data.sql +++ b/tools/migrate/migrate/sql/mysql/00002_init_basic_data.up.sql @@ -1,3 +1,4 @@ +-- 000002_init_data.up.sql SET FOREIGN_KEY_CHECKS = 0; -- auth_method @@ -123,4 +124,4 @@ VALUES (1, 'site', 'SiteLogo', '/favicon.svg', 'string', 'Site Logo', '2025-04-2 '2025-04-22 14:25:16.641'), (41, 'system', 'Version', '0.2.0(02002)', 'string', 'System Version', '2025-04-22 14:25:16.642', '2025-04-22 14:25:16.642'); -SET FOREIGN_KEY_CHECKS = 1; +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/migrations/mysql/02003_update_payment.down.sql b/tools/migrate/migrate/sql/mysql/02003_update_payment.down.sql similarity index 100% rename from migrations/mysql/02003_update_payment.down.sql rename to tools/migrate/migrate/sql/mysql/02003_update_payment.down.sql diff --git a/migrations/mysql/02003_update_payment.sql b/tools/migrate/migrate/sql/mysql/02003_update_payment.up.sql similarity index 99% rename from migrations/mysql/02003_update_payment.sql rename to tools/migrate/migrate/sql/mysql/02003_update_payment.up.sql index 13cf72eb..3991cff1 100644 --- a/migrations/mysql/02003_update_payment.sql +++ b/tools/migrate/migrate/sql/mysql/02003_update_payment.up.sql @@ -69,4 +69,4 @@ PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -SET FOREIGN_KEY_CHECKS = 1; +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/migrations/mysql/02004_rebuild_rule.down.sql b/tools/migrate/migrate/sql/mysql/02004_rebuild_rule.down.sql similarity index 100% rename from migrations/mysql/02004_rebuild_rule.down.sql rename to tools/migrate/migrate/sql/mysql/02004_rebuild_rule.down.sql diff --git a/migrations/mysql/02004_rebuild_rule.sql b/tools/migrate/migrate/sql/mysql/02004_rebuild_rule.up.sql similarity index 97% rename from migrations/mysql/02004_rebuild_rule.sql rename to tools/migrate/migrate/sql/mysql/02004_rebuild_rule.up.sql index be3ead31..72f9b0ff 100644 --- a/migrations/mysql/02004_rebuild_rule.sql +++ b/tools/migrate/migrate/sql/mysql/02004_rebuild_rule.up.sql @@ -19,4 +19,4 @@ CREATE TABLE `server_rule_group` INDEX `idx_enable` (`enable`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci; + COLLATE = utf8mb4_general_ci; \ No newline at end of file diff --git a/migrations/mysql/02005_device_online_record.down.sql b/tools/migrate/migrate/sql/mysql/02005_device_online_record.down.sql similarity index 100% rename from migrations/mysql/02005_device_online_record.down.sql rename to tools/migrate/migrate/sql/mysql/02005_device_online_record.down.sql diff --git a/migrations/mysql/02005_device_online_record.sql b/tools/migrate/migrate/sql/mysql/02005_device_online_record.up.sql similarity index 99% rename from migrations/mysql/02005_device_online_record.sql rename to tools/migrate/migrate/sql/mysql/02005_device_online_record.up.sql index 0d71dbff..e149f121 100644 --- a/migrations/mysql/02005_device_online_record.sql +++ b/tools/migrate/migrate/sql/mysql/02005_device_online_record.up.sql @@ -67,4 +67,3 @@ SET @sql = IF(@column_exists = 0, PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; - diff --git a/migrations/mysql/02006_reset_subscribe_record.down.sql b/tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.down.sql similarity index 100% rename from migrations/mysql/02006_reset_subscribe_record.down.sql rename to tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.down.sql diff --git a/migrations/mysql/02006_reset_subscribe_record.sql b/tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.up.sql similarity index 99% rename from migrations/mysql/02006_reset_subscribe_record.sql rename to tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.up.sql index 53e1d32b..ef5affd1 100644 --- a/migrations/mysql/02006_reset_subscribe_record.sql +++ b/tools/migrate/migrate/sql/mysql/02006_reset_subscribe_record.up.sql @@ -15,4 +15,3 @@ CREATE TABLE IF NOT EXISTS `user_reset_subscribe_log` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; - diff --git a/migrations/mysql/02007_adapte_rule.down.sql b/tools/migrate/migrate/sql/mysql/02007_adapte_rule.down.sql similarity index 100% rename from migrations/mysql/02007_adapte_rule.down.sql rename to tools/migrate/migrate/sql/mysql/02007_adapte_rule.down.sql diff --git a/migrations/mysql/02007_adapte_rule.sql b/tools/migrate/migrate/sql/mysql/02007_adapte_rule.up.sql similarity index 92% rename from migrations/mysql/02007_adapte_rule.sql rename to tools/migrate/migrate/sql/mysql/02007_adapte_rule.up.sql index dd2010d6..0c30d54f 100644 --- a/migrations/mysql/02007_adapte_rule.sql +++ b/tools/migrate/migrate/sql/mysql/02007_adapte_rule.up.sql @@ -1,3 +1,3 @@ ALTER TABLE `server_rule_group` ADD COLUMN `default` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Is Default Group', -ADD COLUMN `type` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Rule Group Type'; +ADD COLUMN `type` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Rule Group Type'; \ No newline at end of file diff --git a/migrations/mysql/02100_task.down.sql b/tools/migrate/migrate/sql/mysql/02100_task.down.sql similarity index 100% rename from migrations/mysql/02100_task.down.sql rename to tools/migrate/migrate/sql/mysql/02100_task.down.sql diff --git a/migrations/mysql/02100_task.sql b/tools/migrate/migrate/sql/mysql/02100_task.up.sql similarity index 99% rename from migrations/mysql/02100_task.sql rename to tools/migrate/migrate/sql/mysql/02100_task.up.sql index 8b4b2d74..d35e8c91 100644 --- a/migrations/mysql/02100_task.sql +++ b/tools/migrate/migrate/sql/mysql/02100_task.up.sql @@ -21,4 +21,3 @@ CREATE TABLE `email_task` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; SET FOREIGN_KEY_CHECKS = 1; - diff --git a/migrations/mysql/02101_subscribe_application.down.sql b/tools/migrate/migrate/sql/mysql/02101_subscribe_application.down.sql similarity index 100% rename from migrations/mysql/02101_subscribe_application.down.sql rename to tools/migrate/migrate/sql/mysql/02101_subscribe_application.down.sql diff --git a/migrations/mysql/02101_subscribe_application.sql b/tools/migrate/migrate/sql/mysql/02101_subscribe_application.up.sql similarity index 99% rename from migrations/mysql/02101_subscribe_application.sql rename to tools/migrate/migrate/sql/mysql/02101_subscribe_application.up.sql index e4a53899..4598dab1 100644 --- a/migrations/mysql/02101_subscribe_application.sql +++ b/tools/migrate/migrate/sql/mysql/02101_subscribe_application.up.sql @@ -25,4 +25,3 @@ INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `schem INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (4, 'SingBox', '', '', 'sing-box://import-remote-profile?url=${encodeURIComponent(url)}#${name}', 'sing-box', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $ExpiredAt := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $ExpiredAt = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $ExpiredAt = $expStr -}}\n{{- end -}}\n\n{{- $sortFields := list \"Sort\" \"Port\" \"Name\" -}}\n{{- $sortConfig := dict \"Sort\" \"asc\" \"Port\" \"asc\" \"Name\" \"asc\" -}}\n{{- $byKey := dict -}}\n{{- range $p := .Proxies -}}\n {{- $keyParts := list -}}\n {{- range $field := $sortFields -}}\n {{- $order := index $sortConfig $field -}}\n {{- $val := default \"\" (printf \"%v\" (index $p $field)) -}}\n {{- if or (eq $field \"Sort\") (eq $field \"Port\") -}}\n {{- $val = printf \"%08d\" (int (default 0 (index $p $field))) -}}\n {{- end -}}\n {{- if eq $order \"desc\" -}}\n {{- $val = printf \"~%s\" $val -}}\n {{- end -}}\n {{- $keyParts = append $keyParts $val -}}\n {{- end -}}\n {{- $_ := set $byKey (join \"|\" $keyParts) $p -}}\n{{- end -}}\n{{- $sorted := list -}}\n{{- range $k := sortAlpha (keys $byKey) -}}\n {{- $sorted = append $sorted (index $byKey $k) -}}\n{{- end -}}\n\n{{- $supportedProxies := list -}}\n{{- range $proxy := $sorted -}}\n {{- $isSupported := false -}}\n {{- if or (eq $proxy.Type \"shadowsocks\") (eq $proxy.Type \"vmess\") (eq $proxy.Type \"trojan\") (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") (eq $proxy.Type \"tuic\") (eq $proxy.Type \"anytls\") -}}\n {{- $isSupported = true -}}\n {{- else if eq $proxy.Type \"vless\" -}}\n {{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") (eq $proxy.Transport \"grpc\") (eq $proxy.Transport \"tcp\") (not $proxy.Transport) -}}\n {{- $isSupported = true -}}\n {{- end -}}\n {{- end -}}\n {{- if $isSupported -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- define \"AllNodeNames\" -}}\n{{- $sortConfig := dict \"Sort\" \"asc\" -}}\n{{- $byKey := dict -}}\n{{- range $p := .Proxies -}}\n {{- $keyParts := list -}}\n {{- range $field, $order := $sortConfig -}}\n {{- $val := default \"\" (printf \"%v\" (index $p $field)) -}}\n {{- if or (eq $field \"Sort\") (eq $field \"Port\") -}}\n {{- $val = printf \"%08d\" (int (default 0 (index $p $field))) -}}\n {{- end -}}\n {{- if eq $order \"desc\" -}}\n {{- $val = printf \"~%s\" $val -}}\n {{- end -}}\n {{- $keyParts = append $keyParts $val -}}\n {{- end -}}\n {{- $_ := set $byKey (join \"|\" $keyParts) $p -}}\n{{- end -}}\n{{- $sorted := list -}}\n{{- range $k := sortAlpha (keys $byKey) -}}\n {{- $sorted = append $sorted (index $byKey $k) -}}\n{{- end -}}\n{{- $supportedProxies := list -}}\n{{- range $proxy := $sorted -}}\n {{- $isSupported := false -}}\n {{- if or (eq .Type \"shadowsocks\") (eq .Type \"vmess\") (eq .Type \"trojan\") (eq .Type \"hysteria2\") (eq .Type \"hy2\") (eq .Type \"tuic\") (eq .Type \"anytls\") -}}\n {{- $isSupported = true -}}\n {{- else if eq .Type \"vless\" -}}\n {{- if or (eq .Transport \"ws\") (eq .Transport \"websocket\") (eq .Transport \"grpc\") (eq .Transport \"tcp\") (not .Transport) -}}\n {{- $isSupported = true -}}\n {{- end -}}\n {{- end -}}\n {{- if $isSupported -}}\n {{- $supportedProxies = append $supportedProxies . -}}\n {{- end -}}\n{{- end -}}\n{{- $first := true -}}\n{{- range $supportedProxies -}}\n {{- if $first -}}\n \"{{ .Name }}\"\n {{- $first = false -}}\n {{- else -}}\n , \"{{ .Name }}\"\n {{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{- define \"NodeOutbound\" -}}\n{{- $proxy := .proxy -}}\n{{- $server := $proxy.Server -}}\n{{- if and (contains $server \":\") (not (hasPrefix \"[\" $server)) -}}\n {{- $server = printf \"[%s]\" $server -}}\n{{- end -}}\n{{- $port := $proxy.Port -}}\n{{- $name := $proxy.Name -}}\n{{- $pwd := $.UserInfo.Password -}}\n{{- $sni := or $proxy.SNI $server }}\n{{- $svc := $proxy.ServiceName }}\n\n{{- $tlsOpts := \"\" -}}\n{{- if or $sni $proxy.AllowInsecure $proxy.Fingerprint -}}\n {{- $tlsOpts = \"\\\"tls\\\": {\\\"enabled\\\": true\" -}}\n {{- if $sni -}}\n {{- $tlsOpts = printf \"%s, \\\"server_name\\\": \\\"%s\\\"\" $tlsOpts $sni -}}\n {{- end -}}\n {{- if $proxy.AllowInsecure -}}\n {{- $tlsOpts = printf \"%s, \\\"insecure\\\": true\" $tlsOpts -}}\n {{- end -}}\n {{- if $proxy.Fingerprint -}}\n {{- $tlsOpts = printf \"%s, \\\"utls\\\": {\\\"enabled\\\": true, \\\"fingerprint\\\": \\\"%s\\\"}\" $tlsOpts ($proxy.Fingerprint) -}}\n {{- end -}}\n {{- $tlsOpts = printf \"%s}\" $tlsOpts -}}\n{{- end -}}\n\n{{- $transportOpts := \"\" -}}\n{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") -}}\n {{- $wsPath := default \"/\" $proxy.Path -}}\n {{- $transportOpts = printf \"\\\"transport\\\": {\\\"type\\\": \\\"ws\\\", \\\"path\\\": \\\"%s\\\"\" $wsPath -}}\n {{- if $proxy.Host -}}\n {{- $transportOpts = printf \"%s, \\\"headers\\\": {\\\"Host\\\": \\\"%s\\\"}\" $transportOpts ($proxy.Host) -}}\n {{- end -}}\n {{- $transportOpts = printf \"%s}\" $transportOpts -}}\n{{- else if eq $proxy.Transport \"grpc\" -}}\n {{- $grpcService := default \"grpc\" $svc -}}\n {{- $transportOpts = printf \"\\\"transport\\\": {\\\"type\\\": \\\"grpc\\\", \\\"service_name\\\": \\\"%s\\\"}\" $grpcService -}}\n{{- end -}}\n\n{{- if eq $proxy.Type \"shadowsocks\" -}}\n {{- $method := default \"aes-128-gcm\" $proxy.Method -}}\n {{- $password := $pwd -}}\n {{- if $proxy.ServerKey -}}\n {{- $needBytes := ternary 16 32 (eq $proxy.Method \"2022-blake3-aes-128-gcm\") -}}\n {{- $cutLen := min $needBytes (len $pwd) | int -}}\n {{- $userCut := $pwd | trunc $cutLen -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userCut -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n{ \"type\": \"shadowsocks\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"method\": \"{{ $method }}\", \"password\": \"{{ $password }}\" }\n\n{{- else if eq $proxy.Type \"trojan\" -}}\n{ \"type\": \"trojan\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"password\": \"{{ $pwd }}\"{{ if $transportOpts }}, {{ $transportOpts }}{{ end }}, {{ $tlsOpts }} }\n\n{{- else if eq $proxy.Type \"vless\" -}}\n{{- $realityOpts := \"\" -}}\n{{- if $proxy.RealityPublicKey -}}\n {{- $realityOpts = printf \"\\\"reality\\\": { \\\"enabled\\\": true, \\\"public_key\\\": \\\"%s\\\"\" ($proxy.RealityPublicKey) -}}\n {{- if $proxy.RealityShortId -}}\n {{- $realityOpts = printf \"%s, \\\"short_id\\\": \\\"%s\\\"\" $realityOpts ($proxy.RealityShortId) -}}\n {{- end -}}\n {{- if $svc -}}\n {{- $realityOpts = printf \"%s, \\\"server_name\\\": \\\"%s\\\"\" $realityOpts ($svc) -}}\n {{- end -}}\n {{- $realityOpts = printf \"%s }\" $realityOpts -}}\n{{- end -}}\n{{- $flowOpts := \"\" -}}\n{{- if $proxy.Flow -}}\n {{- $flowOpts = printf \", \\\"flow\\\": \\\"%s\\\"\" ($proxy.Flow) -}}\n{{- end -}}\n{ \"type\": \"vless\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"uuid\": \"{{ $pwd }}\"{{ $flowOpts }}{{ if $transportOpts }}, {{ $transportOpts }}{{ end }}{{ if $realityOpts }}, {{ $realityOpts }}{{ else if $tlsOpts }}, {{ $tlsOpts }}{{ end }} }\n\n{{- else if eq $proxy.Type \"vmess\" -}}\n{{- $vmessTLS := \"\" -}}\n{{- if and $tlsOpts (ne $proxy.Transport \"tcp\") -}}\n {{- $vmessTLS = $tlsOpts -}}\n{{- end -}}\n{ \"type\": \"vmess\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"uuid\": \"{{ $pwd }}\", \"security\": \"auto\"{{ if $transportOpts }}, {{ $transportOpts }}{{ end }}{{ if $vmessTLS }}, {{ $vmessTLS }}{{ end }} }\n\n{{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hy2\") -}}\n{{- $obfsOpts := \"\" -}}\n{{- if $proxy.ObfsPassword -}}\n {{- $obfsOpts = printf \"\\\"obfs\\\": { \\\"type\\\": \\\"salamander\\\", \\\"password\\\": \\\"%s\\\" }\" ($proxy.ObfsPassword) -}}\n{{- end -}}\n{{- $hopPortsOpts := \"\" -}}\n{{- if $proxy.HopPorts -}}\n {{- $hopPortsOpts = printf \", \\\"ports\\\": \\\"%s\\\"\" ($proxy.HopPorts) -}}\n{{- end -}}\n{{- $hopIntervalOpts := \"\" -}}\n{{- if $proxy.HopInterval -}}\n {{- $hopIntervalOpts = printf \", \\\"hop_interval\\\": %v\" $proxy.HopInterval -}}\n{{- end -}}\n{ \"type\": \"hysteria2\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"password\": \"{{ $pwd }}\"{{ if $obfsOpts }}, {{ $obfsOpts }}{{ end }}{{ $hopPortsOpts }}{{ $hopIntervalOpts }}, {{ $tlsOpts }} }\n\n{{- else if eq $proxy.Type \"tuic\" -}}\n{{- $tuicServerKey := $proxy.ServerKey -}}\n{{- $tuicOpts := \"\" -}}\n{{- if $proxy.DisableSNI -}}\n {{- $tuicOpts = printf \"%s, \\\"disable_sni\\\": %v\" $tuicOpts $proxy.DisableSNI -}}\n{{- end -}}\n{{- if $proxy.ReduceRtt -}}\n {{- $tuicOpts = printf \"%s, \\\"reduce_rtt\\\": %v\" $tuicOpts $proxy.ReduceRtt -}}\n{{- end -}}\n{{- if $proxy.UDPRelayMode -}}\n {{- $tuicOpts = printf \"%s, \\\"udp_relay_mode\\\": \\\"%s\\\"\" $tuicOpts ($proxy.UDPRelayMode) -}}\n{{- end -}}\n{{- if $proxy.CongestionController -}}\n {{- $tuicOpts = printf \"%s, \\\"congestion_control\\\": \\\"%s\\\"\" $tuicOpts ($proxy.CongestionController) -}}\n{{- end -}}\n{ \"type\": \"tuic\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"uuid\": \"{{ $tuicServerKey }}\", \"password\": \"{{ $pwd }}\"{{ $tuicOpts }}, \"alpn\": [\"h3\"], {{ $tlsOpts }} }\n\n{{- else if eq $proxy.Type \"anytls\" -}}\n{{- $anytlsOpts := \"\" -}}\n{{- if $proxy.Method -}}\n {{- $anytlsOpts = printf \"%s, \\\"method\\\": \\\"%s\\\"\" $anytlsOpts ($proxy.Method) -}}\n{{- end -}}\n{{- if $proxy.ObfsPassword -}}\n {{- $anytlsOpts = printf \"%s, \\\"obfs\\\": \\\"%s\\\"\" $anytlsOpts ($proxy.ObfsPassword) -}}\n{{- end -}}\n{{- if $proxy.Path -}}\n {{- $anytlsOpts = printf \"%s, \\\"path\\\": \\\"%s\\\"\" $anytlsOpts ($proxy.Path) -}}\n{{- end -}}\n{{- if $proxy.Host -}}\n {{- $anytlsOpts = printf \"%s, \\\"host\\\": \\\"%s\\\"\" $anytlsOpts ($proxy.Host) -}}\n{{- end -}}\n{ \"type\": \"anytls\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"password\": \"{{ $pwd }}\"{{ $anytlsOpts }}{{ if $tlsOpts }}, {{ $tlsOpts }}{{ end }} }\n\n{{- else if eq $proxy.Type \"wireguard\" -}}\n{{- $wgPrivateKey := $proxy.ServerKey -}}\n{{- $wgPublicKey := $proxy.RealityPublicKey -}}\n{{- $wgPreSharedOpts := \"\" -}}\n{{- if $proxy.Path -}}\n {{- $wgPreSharedOpts = printf \", \\\"pre_shared_key\\\": \\\"%s\\\"\" ($proxy.Path) -}}\n{{- end -}}\n{{- $wgLocalAddressOpts := \"\" -}}\n{{- if $proxy.RealityServerAddr -}}\n {{- $wgLocalAddressOpts = printf \", \\\"local_address\\\": [\\\"%s\\\"]\" ($proxy.RealityServerAddr) -}}\n{{- end -}}\n{ \"type\": \"wireguard\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"private_key\": \"{{ $wgPrivateKey }}\", \"peer_public_key\": \"{{ $wgPublicKey }}\"{{ $wgPreSharedOpts }}{{ $wgLocalAddressOpts }} }\n\n{{- else if or (eq $proxy.Type \"http\") (eq $proxy.Type \"https\") -}}\n{{- $httpsTLSOpts := \"\" -}}\n{{- if and (eq $proxy.Type \"https\") $tlsOpts -}}\n {{- $httpsTLSOpts = printf \", %s\" $tlsOpts -}}\n{{- end -}}\n{ \"type\": \"http\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"username\": \"{{ $pwd }}\", \"password\": \"{{ $pwd }}\"{{ $httpsTLSOpts }} }\n\n{{- else if or (eq $proxy.Type \"socks\") (eq $proxy.Type \"socks5\") -}}\n{ \"type\": \"socks\", \"tag\": \"{{ $name }}\", \"server\": \"{{ $server }}\", \"server_port\": {{ $port }}, \"version\": \"5\", \"username\": \"{{ $pwd }}\", \"password\": \"{{ $pwd }}\" }\n\n{{- else -}}\n{ \"type\": \"direct\", \"tag\": \"{{ $name }}\" }\n{{- end -}}\n{{- end -}}\n\n// 用户信息: 已用流量 {{ $used }}GB / 总流量 {{ $total }}GB, 过期时间: {{ $ExpiredAt }}\n{\n \"log\": {\n \"level\": \"info\",\n \"timestamp\": true\n },\n \"experimental\": {\n \"cache_file\": {\n \"enabled\": true,\n \"store_fakeip\": true,\n \"store_rdrc\": true\n },\n \"clash_api\": {\n \"external_controller\": \"127.0.0.1:9090\",\n \"access_control_allow_origin\": [\n \"http://127.0.0.1\",\n \"https://yacd.metacubex.one\",\n \"https://metacubex.github.io\",\n \"https://metacubexd.pages.dev\",\n \"https://board.zash.run.place\"\n ]\n }\n },\n \"dns\": {\n \"independent_cache\": true,\n \"servers\": [\n {\n \"tag\": \"google\",\n \"type\": \"https\",\n \"server\": \"8.8.8.8\",\n \"detour\": \"节点选择\"\n },\n {\n \"tag\": \"ali\",\n \"type\": \"https\",\n \"server\": \"223.5.5.5\"\n },\n {\n \"tag\": \"fakeip\",\n \"type\": \"fakeip\",\n \"inet4_range\": \"198.18.0.0/15\",\n \"inet6_range\": \"fc00::/18\"\n }\n ],\n \"rules\": [\n {\n \"clash_mode\": \"Direct\",\n \"server\": \"ali\"\n },\n {\n \"clash_mode\": \"Global\",\n \"server\": \"google\"\n },\n {\n \"query_type\": [\n \"A\",\n \"AAAA\"\n ],\n \"server\": \"fakeip\"\n },\n {\n \"rule_set\": \"geosite-cn\",\n \"server\": \"ali\"\n }\n ]\n },\n \"inbounds\": [\n {\n \"type\": \"tun\",\n \"address\": [\n \"172.18.0.1/30\",\n \"fdfe:dcba:9876::1/126\"\n ],\n \"auto_route\": true,\n \"strict_route\": true\n },\n {\n \"type\": \"mixed\",\n \"listen\": \"::\",\n \"listen_port\": 7890\n }\n ],\n \"outbounds\": [\n {\n \"tag\": \"节点选择\",\n \"type\": \"selector\",\n \"outbounds\": [{{ template \"AllNodeNames\" . }}, \"直连\"]\n },\n {\n \"tag\": \"Github\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"Google\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"Microsoft\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"OpenAI\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"Telegram\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"Twitter\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"Youtube\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"节点选择\",\n \"直连\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {\n \"tag\": \"国内\",\n \"type\": \"selector\",\n \"outbounds\": [\n \"直连\",\n \"节点选择\",\n {{ template \"AllNodeNames\" . }}\n ]\n },\n {{- range $i, $proxy := $supportedProxies }}\n {{ if $i }},{{ end }}\n {{ template \"NodeOutbound\" (dict \"proxy\" $proxy \"UserInfo\" $.UserInfo) }}\n {{- end }}\n {{- if gt (len $supportedProxies) 0 }},{{ end }}\n {\n \"tag\": \"直连\",\n \"type\": \"direct\"\n }\n ],\n \"route\": {\n \"default_domain_resolver\": {\n \"server\": \"ali\"\n },\n \"auto_detect_interface\": true,\n \"rules\": [\n {\n \"action\": \"sniff\"\n },\n {\n \"protocol\": \"dns\",\n \"action\": \"hijack-dns\"\n },\n {\n \"ip_is_private\": true,\n \"outbound\": \"直连\"\n },\n {\n \"rule_set\": \"anti-ad\",\n \"clash_mode\": \"Rule\",\n \"action\": \"reject\"\n },\n {\n \"clash_mode\": \"Direct\",\n \"outbound\": \"直连\"\n },\n {\n \"clash_mode\": \"Global\",\n \"outbound\": \"节点选择\"\n },\n {\n \"rule_set\": \"geosite-github\",\n \"outbound\": \"Github\"\n },\n {\n \"rule_set\": [\n \"geoip-google\",\n \"geosite-google\"\n ],\n \"outbound\": \"Google\"\n },\n {\n \"rule_set\": \"geosite-microsoft\",\n \"outbound\": \"Microsoft\"\n },\n {\n \"rule_set\": \"geosite-openai\",\n \"outbound\": \"OpenAI\"\n },\n {\n \"rule_set\": [\n \"geoip-telegram\",\n \"geosite-telegram\"\n ],\n \"outbound\": \"Telegram\"\n },\n {\n \"rule_set\": [\n \"geoip-twitter\",\n \"geosite-twitter\"\n ],\n \"outbound\": \"Twitter\"\n },\n {\n \"rule_set\": \"geosite-youtube\",\n \"outbound\": \"Youtube\"\n },\n {\n \"rule_set\": [\n \"geoip-cn\",\n \"geosite-cn\"\n ],\n \"outbound\": \"国内\"\n }\n ],\n \"rule_set\": [\n {\n \"tag\": \"anti-ad\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://anti-ad.net/anti-ad-sing-box.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-github\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/github.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geoip-google\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geoip/google.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-google\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/google.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-microsoft\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/microsoft.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-openai\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/openai.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geoip-telegram\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geoip/telegram.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-telegram\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/telegram.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geoip-twitter\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geoip/twitter.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-twitter\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/twitter.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-youtube\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/youtube.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geosite-cn\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geosite/cn.srs\",\n \"download_detour\": \"直连\"\n },\n {\n \"tag\": \"geoip-cn\",\n \"type\": \"remote\",\n \"format\": \"binary\",\n \"url\": \"https://cdn.jsdmirror.com/gh/perfect-panel/rules/geo/geoip/cn.srs\",\n \"download_detour\": \"直连\"\n }\n ]\n }\n}', 'json', '{}', '2025-08-12 23:30:10.016', '2025-08-15 22:01:10.801'); INSERT INTO `subscribe_application` (`id`, `name`, `icon`, `description`, `scheme`, `user_agent`, `is_default`, `subscribe_template`, `output_format`, `download_link`, `created_at`, `updated_at`) VALUES (5, 'Surge', '', '', 'surge:///install-config?url=${encodeURIComponent(url)}', 'Surge', 0, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $ExpiredAt := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $ExpiredAt = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $ExpiredAt = $expStr -}}\n{{- end -}}\n\n{{- $sortFields := list \"Sort\" \"Port\" \"Name\" -}}\n{{- $sortConfig := dict \"Sort\" \"asc\" \"Port\" \"asc\" \"Name\" \"asc\" -}}\n{{- $byKey := dict -}}\n{{- range $p := .Proxies -}}\n {{- $keyParts := list -}}\n {{- range $field := $sortFields -}}\n {{- $order := index $sortConfig $field -}}\n {{- $val := default \"\" (printf \"%v\" (index $p $field)) -}}\n {{- if or (eq $field \"Sort\") (eq $field \"Port\") -}}\n {{- $val = printf \"%08d\" (int (default 0 (index $p $field))) -}}\n {{- end -}}\n {{- if eq $order \"desc\" -}}\n {{- $val = printf \"~%s\" $val -}}\n {{- end -}}\n {{- $keyParts = append $keyParts $val -}}\n {{- end -}}\n {{- $_ := set $byKey (join \"|\" $keyParts) $p -}}\n{{- end -}}\n{{- $sorted := list -}}\n{{- range $k := sortAlpha (keys $byKey) -}}\n {{- $sorted = append $sorted (index $byKey $k) -}}\n{{- end -}}\n\n{{- $supportSet := dict \"shadowsocks\" true \"vmess\" true \"vless\" true \"trojan\" true \"hysteria2\" true \"hysteria\" true \"tuic\" true \"wireguard\" true -}}\n{{- $supportedProxies := list -}}\n{{- range $proxy := $sorted -}}\n {{- if hasKey $supportSet $proxy.Type -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- $proxyNames := \"\" -}}\n{{- range $proxy := $supportedProxies -}}\n {{- if eq $proxyNames \"\" -}}\n {{- $proxyNames = $proxy.Name -}}\n {{- else -}}\n {{- $proxyNames = printf \"%s, %s\" $proxyNames $proxy.Name -}}\n {{- end -}}\n{{- end -}}\n\n# {{ .SiteName }}-{{ .SubscribeName }}\n# Traffic: {{ $used }} GiB/{{ $total }} GiB | Expires: {{ $ExpiredAt }}\n# Generated at: {{ now | date \"2006-01-02 15:04:05\" }}\n\n#!MANAGED-CONFIG {{ .UserInfo.SubscribeURL }} interval=86400 strict=true\n\n[General]\n# 日志级别\nloglevel = notify\n\n# 外部控制器访问\nexternal-controller-access = perlnk@0.0.0.0:6170\n\n# 网络设置\nexclude-simple-hostnames = true\nshow-error-page-for-reject = true\nudp-priority = true\nudp-policy-not-supported-behaviour = reject\nipv6 = true\nipv6-vif = auto\n\n# 连接测试\nproxy-test-url = http://www.gstatic.com/generate_204\ninternet-test-url = http://www.gstatic.com/generate_204\ntest-timeout = 5\n\n# DNS 设置\ndns-server = system, 119.29.29.29, 223.5.5.5\nencrypted-dns-server = https://dns.alidns.com/dns-query\nhijack-dns = 8.8.8.8:53, 8.8.4.4:53, 1.1.1.1:53, 1.0.0.1:53\n\n# 跳过代理\nskip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.0/8, localhost, *.local\n\n# 真实 IP\nalways-real-ip = *.lan, lens.l.google.com, *.srv.nintendo.net, *.stun.playstation.net, *.xboxlive.com, xbox.*.*.microsoft.com, *.msftncsi.com, *.msftconnecttest.com\n\n# Surge Mac 参数\nhttp-listen = 0.0.0.0:6088\nsocks5-listen = 0.0.0.0:6089\n\n# Surge iOS 参数(WiFi 共享)\nallow-wifi-access = true\nallow-hotspot-access = true\nwifi-access-http-port = 6088\nwifi-access-socks5-port = 6089\n\n[Panel]\nSubscribeInfo = title={{ .SiteName }} - {{ .SubscribeName }}, content=已用流量: {{ $used }} GiB/{{ $total }} GiB \\n到期时间: {{ $ExpiredAt}}, style=info\n\n[Proxy]\n{{- range $proxy := $supportedProxies }}\n {{- $common := \"udp-relay=true, tfo=true\" -}}\n\n {{- $server := $proxy.Server -}}\n {{- if and (contains $server \":\") (not (hasPrefix \"[\" $server)) -}}\n {{- $server = printf \"[%s]\" $server -}}\n {{- end -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- $SkipVerify := $proxy.AllowInsecure -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\n{{ $proxy.Name }} = ss, {{ $server }}, {{ $proxy.Port }}, encrypt-method={{ default \"aes-128-gcm\" $proxy.Method }}, password={{ $password }}{{- if ne (default \"\" $proxy.Obfs) \"\" }}, obfs={{ $proxy.Obfs }}{{- if ne (default \"\" $proxy.ObfsHost) \"\" }}, obfs-host={{ $proxy.ObfsHost }}{{- end }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vmess\" }}\n{{ $proxy.Name }} = vmess, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}, tls=true{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vless\" }}\n{{ $proxy.Name }} = vless, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Flow) \"none\" }}, flow={{ $proxy.Flow }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"trojan\" }}\n{{ $proxy.Name }} = trojan, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hysteria\") }}\n{{ $proxy.Name }} = hysteria2, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}, obfs=salamander, obfs-password={{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}, ports={{ $proxy.HopPorts }}{{- end }}{{- if ne (default 0 $proxy.HopInterval) 0 }}, hop-interval={{ $proxy.HopInterval }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"tuic\" }}\n{{ $proxy.Name }} = tuic, {{ $server }}, {{ $proxy.Port }}, uuid={{ default \"\" $proxy.ServerKey }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if $proxy.DisableSNI }}, disable-sni=true{{- end }}{{- if $proxy.ReduceRtt }}, reduce-rtt=true{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}, udp-relay-mode={{ $proxy.UDPRelayMode }}{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}, congestion-controller={{ $proxy.CongestionController }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"wireguard\" }}\n{{ $proxy.Name }} = wireguard, {{ $server }}, {{ $proxy.Port }}, private-key={{ default \"\" $proxy.ServerKey }}, public-key={{ default \"\" $proxy.RealityPublicKey }}{{- if ne (default \"\" $proxy.Path) \"\" }}, preshared-key={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.RealityServerAddr) \"\" }}, ip={{ $proxy.RealityServerAddr }}{{- end }}{{- if ne (default 0 $proxy.RealityServerPort) 0 }}, ipv6={{ $proxy.RealityServerPort }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"anytls\" }}\n{{ $proxy.Name }} = anytls, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}, {{ $common }}\n {{- else }}\n{{ $proxy.Name }} = {{ $proxy.Type }}, {{ $server }}, {{ $proxy.Port }}, {{ $common }}\n {{- end }}\n{{- end }}\n\n[Proxy Group]\n# 主要策略组\n🚀 Proxy = select, 🌏 Auto, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🍎 Apple = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🔍 Google = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪟 Microsoft = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📺 GlobalMedia = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🤖 AI = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪙 Crypto = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🎮 Game = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📟 Telegram = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🇨🇳 China = select, 🎯 Direct, 🚀 Proxy, include-other-group=🇺🇳 Nodes\n🐠 Final = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n\n# 智能选择和节点组\n🌏 Auto = smart, include-other-group=🇺🇳 Nodes\n🎯 Direct = select, DIRECT, hidden=1\n🇺🇳 Nodes = select, {{ $proxyNames }}, hidden=1\n\n[Rule]\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Apple/Apple_All.list, 🍎 Apple\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Google/Google.list, 🔍 Google\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/GitHub/GitHub.list, 🪟 Microsoft\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Microsoft/Microsoft.list, 🪟 Microsoft\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/HBO/HBO.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Disney/Disney.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/TikTok/TikTok.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Netflix/Netflix.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/GlobalMedia/GlobalMedia_All_No_Resolve.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Telegram/Telegram.list, 📟 Telegram\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/OpenAI/OpenAI.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Gemini/Gemini.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Copilot/Copilot.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Claude/Claude.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Crypto/Crypto.list, 🪙 Crypto\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Cryptocurrency/Cryptocurrency.list, 🪙 Crypto\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Game/Game.list, 🎮 Game\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Global/Global_All_No_Resolve.list, 🚀 Proxy\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/ChinaMax/ChinaMax_All_No_Resolve.list, 🇨🇳 China\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Lan/Lan.list, 🎯 Direct\n\nGEOIP, CN, 🇨🇳 China\nFINAL, 🐠 Final, dns-failed\n\n[URL Rewrite]\n^https?:\\/\\/(www.)?g\\.cn https://www.google.com 302\n^https?:\\/\\/(www.)?google\\.cn https://www.google.com 302\n', 'conf', '{}', '2025-08-13 00:12:37.809', '2025-08-15 22:00:50.528'); COMMIT; - diff --git a/migrations/mysql/02102_subscribe_config.down.sql b/tools/migrate/migrate/sql/mysql/02102_subscribe_config.down.sql similarity index 100% rename from migrations/mysql/02102_subscribe_config.down.sql rename to tools/migrate/migrate/sql/mysql/02102_subscribe_config.down.sql diff --git a/migrations/mysql/02102_subscribe_config.sql b/tools/migrate/migrate/sql/mysql/02102_subscribe_config.up.sql similarity index 83% rename from migrations/mysql/02102_subscribe_config.sql rename to tools/migrate/migrate/sql/mysql/02102_subscribe_config.up.sql index edb6bfce..44601004 100644 --- a/migrations/mysql/02102_subscribe_config.sql +++ b/tools/migrate/migrate/sql/mysql/02102_subscribe_config.up.sql @@ -1,4 +1,4 @@ INSERT IGNORE INTO `system` (`id`, `category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES (42, 'subscribe', 'UserAgentLimit', 'false', 'bool', 'User Agent Limit', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), - (43, 'subscribe', 'UserAgentList', '', 'string', 'User Agent List', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); + (43, 'subscribe', 'UserAgentList', '', 'string', 'User Agent List', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/migrations/mysql/02103_delete_application.down.sql b/tools/migrate/migrate/sql/mysql/02103_delete_application.down.sql similarity index 100% rename from migrations/mysql/02103_delete_application.down.sql rename to tools/migrate/migrate/sql/mysql/02103_delete_application.down.sql diff --git a/migrations/mysql/02103_delete_application.sql b/tools/migrate/migrate/sql/mysql/02103_delete_application.up.sql similarity index 65% rename from migrations/mysql/02103_delete_application.sql rename to tools/migrate/migrate/sql/mysql/02103_delete_application.up.sql index 648ad651..1a3a778f 100644 --- a/migrations/mysql/02103_delete_application.sql +++ b/tools/migrate/migrate/sql/mysql/02103_delete_application.up.sql @@ -1,3 +1,3 @@ DROP TABLE IF EXISTS `application`; DROP TABLE IF EXISTS `application_version`; -DROP TABLE IF EXISTS `application_config`; +DROP TABLE IF EXISTS `application_config`; \ No newline at end of file diff --git a/migrations/mysql/02104_system_log.down.sql b/tools/migrate/migrate/sql/mysql/02104_system_log.down.sql similarity index 100% rename from migrations/mysql/02104_system_log.down.sql rename to tools/migrate/migrate/sql/mysql/02104_system_log.down.sql diff --git a/migrations/mysql/02104_system_log.sql b/tools/migrate/migrate/sql/mysql/02104_system_log.up.sql similarity index 99% rename from migrations/mysql/02104_system_log.sql rename to tools/migrate/migrate/sql/mysql/02104_system_log.up.sql index 75b6de39..b518e681 100644 --- a/migrations/mysql/02104_system_log.sql +++ b/tools/migrate/migrate/sql/mysql/02104_system_log.up.sql @@ -16,4 +16,4 @@ CREATE TABLE `system_logs` ( PRIMARY KEY (`id`), KEY `idx_type` (`type`), KEY `idx_object_id` (`object_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/migrations/mysql/02105_node.down.sql b/tools/migrate/migrate/sql/mysql/02105_node.down.sql similarity index 100% rename from migrations/mysql/02105_node.down.sql rename to tools/migrate/migrate/sql/mysql/02105_node.down.sql diff --git a/migrations/mysql/02105_node.sql b/tools/migrate/migrate/sql/mysql/02105_node.up.sql similarity index 99% rename from migrations/mysql/02105_node.sql rename to tools/migrate/migrate/sql/mysql/02105_node.up.sql index 44073729..c9c310b5 100644 --- a/migrations/mysql/02105_node.sql +++ b/tools/migrate/migrate/sql/mysql/02105_node.up.sql @@ -26,4 +26,3 @@ CREATE TABLE IF NOT EXISTS `nodes` ( `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - diff --git a/migrations/mysql/02106_subscribe.down.sql b/tools/migrate/migrate/sql/mysql/02106_subscribe.down.sql similarity index 100% rename from migrations/mysql/02106_subscribe.down.sql rename to tools/migrate/migrate/sql/mysql/02106_subscribe.down.sql diff --git a/migrations/mysql/02106_subscribe.sql b/tools/migrate/migrate/sql/mysql/02106_subscribe.up.sql similarity index 99% rename from migrations/mysql/02106_subscribe.sql rename to tools/migrate/migrate/sql/mysql/02106_subscribe.up.sql index f6bad22d..28f5db5a 100644 --- a/migrations/mysql/02106_subscribe.sql +++ b/tools/migrate/migrate/sql/mysql/02106_subscribe.up.sql @@ -5,4 +5,3 @@ DROP COLUMN `server`, DROP COLUMN `server_group`; DROP TABLE IF EXISTS `server_rule_group`; - diff --git a/migrations/mysql/02107_log_setting.down.sql b/tools/migrate/migrate/sql/mysql/02107_log_setting.down.sql similarity index 100% rename from migrations/mysql/02107_log_setting.down.sql rename to tools/migrate/migrate/sql/mysql/02107_log_setting.down.sql diff --git a/migrations/mysql/02107_log_setting.sql b/tools/migrate/migrate/sql/mysql/02107_log_setting.up.sql similarity index 87% rename from migrations/mysql/02107_log_setting.sql rename to tools/migrate/migrate/sql/mysql/02107_log_setting.up.sql index f5981c0b..c1bc8e21 100644 --- a/migrations/mysql/02107_log_setting.sql +++ b/tools/migrate/migrate/sql/mysql/02107_log_setting.up.sql @@ -1,4 +1,4 @@ INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES ('log', 'AutoClear', 'true', 'bool', 'Auto Clear Log', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), - ('log', 'ClearDays', '7', 'int', 'Clear Days', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); + ('log', 'ClearDays', '7', 'int', 'Clear Days', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/migrations/mysql/02108_user_referral.down.sql b/tools/migrate/migrate/sql/mysql/02108_user_referral.down.sql similarity index 100% rename from migrations/mysql/02108_user_referral.down.sql rename to tools/migrate/migrate/sql/mysql/02108_user_referral.down.sql diff --git a/migrations/mysql/02108_user_referral.sql b/tools/migrate/migrate/sql/mysql/02108_user_referral.up.sql similarity index 99% rename from migrations/mysql/02108_user_referral.sql rename to tools/migrate/migrate/sql/mysql/02108_user_referral.up.sql index 4bd4a020..e50f765e 100644 --- a/migrations/mysql/02108_user_referral.sql +++ b/tools/migrate/migrate/sql/mysql/02108_user_referral.up.sql @@ -5,4 +5,3 @@ ALTER TABLE `user` ADD COLUMN `only_first_purchase` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Only First Purchase' AFTER `referral_percentage`; - diff --git a/migrations/mysql/02109_node_sort.down.sql b/tools/migrate/migrate/sql/mysql/02109_node_sort.down.sql similarity index 100% rename from migrations/mysql/02109_node_sort.down.sql rename to tools/migrate/migrate/sql/mysql/02109_node_sort.down.sql diff --git a/migrations/mysql/02109_node_sort.sql b/tools/migrate/migrate/sql/mysql/02109_node_sort.up.sql similarity index 67% rename from migrations/mysql/02109_node_sort.sql rename to tools/migrate/migrate/sql/mysql/02109_node_sort.up.sql index 49ea18b0..1a993d06 100644 --- a/migrations/mysql/02109_node_sort.sql +++ b/tools/migrate/migrate/sql/mysql/02109_node_sort.up.sql @@ -1,3 +1,3 @@ ALTER TABLE `nodes` ADD COLUMN `sort` INT UNSIGNED NOT NULL DEFAULT 0 - COMMENT 'Sort' AFTER `enabled`; + COMMENT 'Sort' AFTER `enabled`; \ No newline at end of file diff --git a/migrations/mysql/02110_traffic_log_index.down.sql b/tools/migrate/migrate/sql/mysql/02110_traffic_log_index.down.sql similarity index 100% rename from migrations/mysql/02110_traffic_log_index.down.sql rename to tools/migrate/migrate/sql/mysql/02110_traffic_log_index.down.sql diff --git a/migrations/mysql/02110_traffic_log_index.sql b/tools/migrate/migrate/sql/mysql/02110_traffic_log_index.up.sql similarity index 98% rename from migrations/mysql/02110_traffic_log_index.sql rename to tools/migrate/migrate/sql/mysql/02110_traffic_log_index.up.sql index 411378c5..2cf61f24 100644 --- a/migrations/mysql/02110_traffic_log_index.sql +++ b/tools/migrate/migrate/sql/mysql/02110_traffic_log_index.up.sql @@ -1,2 +1 @@ CREATE INDEX idx_traffic_log_time_user_sub ON traffic_log (timestamp, user_id, subscribe_id); - diff --git a/migrations/mysql/02111_clear_table.down.sql b/tools/migrate/migrate/sql/mysql/02111_clear_table.down.sql similarity index 100% rename from migrations/mysql/02111_clear_table.down.sql rename to tools/migrate/migrate/sql/mysql/02111_clear_table.down.sql diff --git a/migrations/mysql/02111_clear_table.sql b/tools/migrate/migrate/sql/mysql/02111_clear_table.up.sql similarity index 58% rename from migrations/mysql/02111_clear_table.sql rename to tools/migrate/migrate/sql/mysql/02111_clear_table.up.sql index 94c2cb18..85c9f3ff 100644 --- a/migrations/mysql/02111_clear_table.sql +++ b/tools/migrate/migrate/sql/mysql/02111_clear_table.up.sql @@ -1,2 +1,2 @@ DROP TABLE IF EXISTS `subscribe_type`; -DROP TABLE IF EXISTS `sms`; +DROP TABLE IF EXISTS `sms`; \ No newline at end of file diff --git a/migrations/mysql/02112_subscribe.down.sql b/tools/migrate/migrate/sql/mysql/02112_subscribe.down.sql similarity index 100% rename from migrations/mysql/02112_subscribe.down.sql rename to tools/migrate/migrate/sql/mysql/02112_subscribe.down.sql diff --git a/migrations/mysql/02112_subscribe.sql b/tools/migrate/migrate/sql/mysql/02112_subscribe.up.sql similarity index 77% rename from migrations/mysql/02112_subscribe.sql rename to tools/migrate/migrate/sql/mysql/02112_subscribe.up.sql index 83127f5a..1a79dbc9 100644 --- a/migrations/mysql/02112_subscribe.sql +++ b/tools/migrate/migrate/sql/mysql/02112_subscribe.up.sql @@ -4,4 +4,4 @@ ADD COLUMN `language` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Language' AFTER `name`; -DROP TABLE IF EXISTS `subscribe_group`; +DROP TABLE IF EXISTS `subscribe_group`; \ No newline at end of file diff --git a/migrations/mysql/02113_task.down.sql b/tools/migrate/migrate/sql/mysql/02113_task.down.sql similarity index 100% rename from migrations/mysql/02113_task.down.sql rename to tools/migrate/migrate/sql/mysql/02113_task.down.sql diff --git a/migrations/mysql/02113_task.sql b/tools/migrate/migrate/sql/mysql/02113_task.up.sql similarity index 99% rename from migrations/mysql/02113_task.sql rename to tools/migrate/migrate/sql/mysql/02113_task.up.sql index 40eeff3d..4e7b1708 100644 --- a/migrations/mysql/02113_task.sql +++ b/tools/migrate/migrate/sql/mysql/02113_task.up.sql @@ -11,4 +11,4 @@ CREATE TABLE `task` ( `created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time', `updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time', PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/migrations/mysql/02114_node_config.down.sql b/tools/migrate/migrate/sql/mysql/02114_node_config.down.sql similarity index 100% rename from migrations/mysql/02114_node_config.down.sql rename to tools/migrate/migrate/sql/mysql/02114_node_config.down.sql diff --git a/migrations/mysql/02114_node_config.sql b/tools/migrate/migrate/sql/mysql/02114_node_config.up.sql similarity index 92% rename from migrations/mysql/02114_node_config.sql rename to tools/migrate/migrate/sql/mysql/02114_node_config.up.sql index 9b8a50fd..12896472 100644 --- a/migrations/mysql/02114_node_config.sql +++ b/tools/migrate/migrate/sql/mysql/02114_node_config.up.sql @@ -5,4 +5,4 @@ VALUE ('server', 'IPStrategy', '', 'string', 'IP Strategy', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), ('server', 'DNS', '', 'string', 'DNS', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), ('server', 'Block', '', 'string', 'Block', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), - ('server', 'Outbound', '', 'string', 'Proxy Outbound', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); + ('server', 'Outbound', '', 'string', 'Proxy Outbound', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/migrations/mysql/02115_ads.down.sql b/tools/migrate/migrate/sql/mysql/02115_ads.down.sql similarity index 100% rename from migrations/mysql/02115_ads.down.sql rename to tools/migrate/migrate/sql/mysql/02115_ads.down.sql diff --git a/migrations/mysql/02115_ads.sql b/tools/migrate/migrate/sql/mysql/02115_ads.up.sql similarity index 99% rename from migrations/mysql/02115_ads.sql rename to tools/migrate/migrate/sql/mysql/02115_ads.up.sql index b9d864d7..39cfaf26 100644 --- a/migrations/mysql/02115_ads.sql +++ b/tools/migrate/migrate/sql/mysql/02115_ads.up.sql @@ -18,4 +18,3 @@ SET PREPARE stmt FROM @query; EXECUTE stmt; DEALLOCATE PREPARE stmt; - diff --git a/migrations/mysql/02116_user_algo.down.sql b/tools/migrate/migrate/sql/mysql/02116_user_algo.down.sql similarity index 100% rename from migrations/mysql/02116_user_algo.down.sql rename to tools/migrate/migrate/sql/mysql/02116_user_algo.down.sql diff --git a/migrations/mysql/02116_user_algo.sql b/tools/migrate/migrate/sql/mysql/02116_user_algo.up.sql similarity index 99% rename from migrations/mysql/02116_user_algo.sql rename to tools/migrate/migrate/sql/mysql/02116_user_algo.up.sql index 4fc42a22..4a79ef2d 100644 --- a/migrations/mysql/02116_user_algo.sql +++ b/tools/migrate/migrate/sql/mysql/02116_user_algo.up.sql @@ -33,4 +33,3 @@ SET @sql = ( PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; - diff --git a/migrations/mysql/02117_site_custom_data.down.sql b/tools/migrate/migrate/sql/mysql/02117_site_custom_data.down.sql similarity index 100% rename from migrations/mysql/02117_site_custom_data.down.sql rename to tools/migrate/migrate/sql/mysql/02117_site_custom_data.down.sql diff --git a/migrations/mysql/02117_site_custom_data.sql b/tools/migrate/migrate/sql/mysql/02117_site_custom_data.up.sql similarity index 99% rename from migrations/mysql/02117_site_custom_data.sql rename to tools/migrate/migrate/sql/mysql/02117_site_custom_data.up.sql index f742bdcc..c8581e8e 100644 --- a/migrations/mysql/02117_site_custom_data.sql +++ b/tools/migrate/migrate/sql/mysql/02117_site_custom_data.up.sql @@ -5,4 +5,3 @@ SELECT 'site', 'CustomData', '{ WHERE NOT EXISTS ( SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData' ); - diff --git a/migrations/mysql/02118_traffic_log_idx.down.sql b/tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.down.sql similarity index 100% rename from migrations/mysql/02118_traffic_log_idx.down.sql rename to tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.down.sql diff --git a/migrations/mysql/02118_traffic_log_idx.sql b/tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.up.sql similarity index 98% rename from migrations/mysql/02118_traffic_log_idx.sql rename to tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.up.sql index 11df14f3..cdd308f6 100644 --- a/migrations/mysql/02118_traffic_log_idx.sql +++ b/tools/migrate/migrate/sql/mysql/02118_traffic_log_idx.up.sql @@ -1,2 +1 @@ ALTER TABLE traffic_log ADD INDEX idx_timestamp (timestamp); - diff --git a/migrations/mysql/02119_user_subscribe_note.down.sql b/tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.down.sql similarity index 100% rename from migrations/mysql/02119_user_subscribe_note.down.sql rename to tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.down.sql diff --git a/migrations/mysql/02119_user_subscribe_note.sql b/tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.up.sql similarity index 99% rename from migrations/mysql/02119_user_subscribe_note.sql rename to tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.up.sql index 821806b8..b8b69838 100644 --- a/migrations/mysql/02119_user_subscribe_note.sql +++ b/tools/migrate/migrate/sql/mysql/02119_user_subscribe_note.up.sql @@ -2,4 +2,3 @@ ALTER TABLE `user_subscribe` ADD COLUMN `note` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'User note for subscription' AFTER `status`; - diff --git a/migrations/mysql/02120_user_rules.down.sql b/tools/migrate/migrate/sql/mysql/02120_user_rules.down.sql similarity index 100% rename from migrations/mysql/02120_user_rules.down.sql rename to tools/migrate/migrate/sql/mysql/02120_user_rules.down.sql diff --git a/migrations/mysql/02120_user_rules.sql b/tools/migrate/migrate/sql/mysql/02120_user_rules.up.sql similarity index 99% rename from migrations/mysql/02120_user_rules.sql rename to tools/migrate/migrate/sql/mysql/02120_user_rules.up.sql index 454c24f4..5e93aca7 100644 --- a/migrations/mysql/02120_user_rules.sql +++ b/tools/migrate/migrate/sql/mysql/02120_user_rules.up.sql @@ -2,4 +2,3 @@ ALTER TABLE `user` ADD COLUMN `rules` TEXT NULL COMMENT 'User rules for subscription' AFTER `created_at`; - diff --git a/migrations/mysql/02121_user_withdrawal.down.sql b/tools/migrate/migrate/sql/mysql/02121_user_withdrawal.down.sql similarity index 100% rename from migrations/mysql/02121_user_withdrawal.down.sql rename to tools/migrate/migrate/sql/mysql/02121_user_withdrawal.down.sql diff --git a/migrations/mysql/02121_user_withdrawal.sql b/tools/migrate/migrate/sql/mysql/02121_user_withdrawal.up.sql similarity index 92% rename from migrations/mysql/02121_user_withdrawal.sql rename to tools/migrate/migrate/sql/mysql/02121_user_withdrawal.up.sql index 212836d9..4f39e1e5 100644 --- a/migrations/mysql/02121_user_withdrawal.sql +++ b/tools/migrate/migrate/sql/mysql/02121_user_withdrawal.up.sql @@ -13,4 +13,4 @@ CREATE TABLE IF NOT EXISTS `withdrawals` ( INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES - ('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'); + ('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/migrations/mysql/02122_server.down.sql b/tools/migrate/migrate/sql/mysql/02122_server.down.sql similarity index 100% rename from migrations/mysql/02122_server.down.sql rename to tools/migrate/migrate/sql/mysql/02122_server.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02122_server.up.sql b/tools/migrate/migrate/sql/mysql/02122_server.up.sql new file mode 100644 index 00000000..2e506e1e --- /dev/null +++ b/tools/migrate/migrate/sql/mysql/02122_server.up.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `server`; \ No newline at end of file diff --git a/migrations/mysql/02123_subscribe_original.down.sql b/tools/migrate/migrate/sql/mysql/02123_subscribe_original.down.sql similarity index 100% rename from migrations/mysql/02123_subscribe_original.down.sql rename to tools/migrate/migrate/sql/mysql/02123_subscribe_original.down.sql diff --git a/migrations/mysql/02123_subscribe_original.sql b/tools/migrate/migrate/sql/mysql/02123_subscribe_original.up.sql similarity index 99% rename from migrations/mysql/02123_subscribe_original.sql rename to tools/migrate/migrate/sql/mysql/02123_subscribe_original.up.sql index 4a0229e1..af04a8bd 100644 --- a/migrations/mysql/02123_subscribe_original.sql +++ b/tools/migrate/migrate/sql/mysql/02123_subscribe_original.up.sql @@ -1,3 +1,2 @@ ALTER TABLE `subscribe` ADD COLUMN `show_original_price` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'display the original price: 0 not display, 1 display' AFTER `created_at`; - diff --git a/migrations/mysql/02124_server_group_delete.down.sql b/tools/migrate/migrate/sql/mysql/02124_server_group_delete.down.sql similarity index 100% rename from migrations/mysql/02124_server_group_delete.down.sql rename to tools/migrate/migrate/sql/mysql/02124_server_group_delete.down.sql diff --git a/tools/migrate/migrate/sql/mysql/02124_server_group_delete.up.sql b/tools/migrate/migrate/sql/mysql/02124_server_group_delete.up.sql new file mode 100644 index 00000000..57c0b5a8 --- /dev/null +++ b/tools/migrate/migrate/sql/mysql/02124_server_group_delete.up.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `server_group`; \ No newline at end of file diff --git a/migrations/mysql/02125_subscribe_stock.down.sql b/tools/migrate/migrate/sql/mysql/02125_subscribe_stock.down.sql similarity index 100% rename from migrations/mysql/02125_subscribe_stock.down.sql rename to tools/migrate/migrate/sql/mysql/02125_subscribe_stock.down.sql diff --git a/migrations/mysql/02125_subscribe_stock.sql b/tools/migrate/migrate/sql/mysql/02125_subscribe_stock.up.sql similarity index 84% rename from migrations/mysql/02125_subscribe_stock.sql rename to tools/migrate/migrate/sql/mysql/02125_subscribe_stock.up.sql index c347495d..88fead18 100644 --- a/migrations/mysql/02125_subscribe_stock.sql +++ b/tools/migrate/migrate/sql/mysql/02125_subscribe_stock.up.sql @@ -1,4 +1,4 @@ -- Update the `subscribe` table to set `inventory` to -1 where it is currently 0 UPDATE `subscribe` SET `inventory` = -1 -WHERE `inventory` = 0; +WHERE `inventory` = 0; \ No newline at end of file diff --git a/migrations/mysql/02126_system_log_idx.down.sql b/tools/migrate/migrate/sql/mysql/02126_system_log_idx.down.sql similarity index 100% rename from migrations/mysql/02126_system_log_idx.down.sql rename to tools/migrate/migrate/sql/mysql/02126_system_log_idx.down.sql diff --git a/migrations/mysql/02126_system_log_idx.sql b/tools/migrate/migrate/sql/mysql/02126_system_log_idx.up.sql similarity index 98% rename from migrations/mysql/02126_system_log_idx.sql rename to tools/migrate/migrate/sql/mysql/02126_system_log_idx.up.sql index eff09df8..6b2e0399 100644 --- a/migrations/mysql/02126_system_log_idx.sql +++ b/tools/migrate/migrate/sql/mysql/02126_system_log_idx.up.sql @@ -1,2 +1 @@ CREATE INDEX idx_type_date ON system_logs (type, date); - diff --git a/migrations/mysql/02127_search_indexes.down.sql b/tools/migrate/migrate/sql/mysql/02127_search_indexes.down.sql similarity index 100% rename from migrations/mysql/02127_search_indexes.down.sql rename to tools/migrate/migrate/sql/mysql/02127_search_indexes.down.sql diff --git a/migrations/mysql/02127_search_indexes.sql b/tools/migrate/migrate/sql/mysql/02127_search_indexes.up.sql similarity index 99% rename from migrations/mysql/02127_search_indexes.sql rename to tools/migrate/migrate/sql/mysql/02127_search_indexes.up.sql index b1894c2b..499a95fb 100644 --- a/migrations/mysql/02127_search_indexes.sql +++ b/tools/migrate/migrate/sql/mysql/02127_search_indexes.up.sql @@ -129,4 +129,3 @@ SET @sql = IF(@index_exists = 0, PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; - diff --git a/migrations/mysql/02128_server_config_override.down.sql b/tools/migrate/migrate/sql/mysql/02128_server_config_override.down.sql similarity index 100% rename from migrations/mysql/02128_server_config_override.down.sql rename to tools/migrate/migrate/sql/mysql/02128_server_config_override.down.sql diff --git a/migrations/mysql/02128_server_config_override.sql b/tools/migrate/migrate/sql/mysql/02128_server_config_override.up.sql similarity index 99% rename from migrations/mysql/02128_server_config_override.sql rename to tools/migrate/migrate/sql/mysql/02128_server_config_override.up.sql index 0227f1d1..a3160597 100644 --- a/migrations/mysql/02128_server_config_override.sql +++ b/tools/migrate/migrate/sql/mysql/02128_server_config_override.up.sql @@ -13,4 +13,3 @@ CREATE TABLE IF NOT EXISTS `server_config_overrides` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci; - diff --git a/migrations/mysql/02129_payment_sort.down.sql b/tools/migrate/migrate/sql/mysql/02129_payment_sort.down.sql similarity index 100% rename from migrations/mysql/02129_payment_sort.down.sql rename to tools/migrate/migrate/sql/mysql/02129_payment_sort.down.sql diff --git a/migrations/mysql/02129_payment_sort.sql b/tools/migrate/migrate/sql/mysql/02129_payment_sort.up.sql similarity index 99% rename from migrations/mysql/02129_payment_sort.sql rename to tools/migrate/migrate/sql/mysql/02129_payment_sort.up.sql index def5a4d7..635ff1ce 100644 --- a/migrations/mysql/02129_payment_sort.sql +++ b/tools/migrate/migrate/sql/mysql/02129_payment_sort.up.sql @@ -19,4 +19,3 @@ DEALLOCATE PREPARE stmt; UPDATE `payment` SET `sort` = `id` WHERE `sort` = 0; - diff --git a/migrations/mysql/02130_subscribe_tutorial.down.sql b/tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.down.sql similarity index 100% rename from migrations/mysql/02130_subscribe_tutorial.down.sql rename to tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.down.sql diff --git a/migrations/mysql/02130_subscribe_tutorial.sql b/tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.up.sql similarity index 99% rename from migrations/mysql/02130_subscribe_tutorial.sql rename to tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.up.sql index 81ecf75b..b4957107 100644 --- a/migrations/mysql/02130_subscribe_tutorial.sql +++ b/tools/migrate/migrate/sql/mysql/02130_subscribe_tutorial.up.sql @@ -3,4 +3,3 @@ SELECT 'subscribe', 'ShowTutorial', 'true', 'bool', 'Show tutorial section on th WHERE NOT EXISTS ( SELECT 1 FROM `system` WHERE `category` = 'subscribe' AND `key` = 'ShowTutorial' ); - diff --git a/migrations/mysql/02131_timestamptz_last_reported_at.down.sql b/tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql similarity index 100% rename from migrations/mysql/02131_timestamptz_last_reported_at.down.sql rename to tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.down.sql diff --git a/migrations/mysql/02131_timestamptz_last_reported_at.sql b/tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql similarity index 99% rename from migrations/mysql/02131_timestamptz_last_reported_at.sql rename to tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql index b929aab5..f8234705 100644 --- a/migrations/mysql/02131_timestamptz_last_reported_at.sql +++ b/tools/migrate/migrate/sql/mysql/02131_timestamptz_last_reported_at.up.sql @@ -2,4 +2,3 @@ -- The Go code fix (serverPushStatusLogic.go, serverPushUserTrafficLogic.go) -- removing .UTC() is sufficient for MySQL environments. SELECT 1; - diff --git a/migrations/postgres/00001_init_schema.down.sql b/tools/migrate/migrate/sql/postgres/00001_init_schema.down.sql similarity index 100% rename from migrations/postgres/00001_init_schema.down.sql rename to tools/migrate/migrate/sql/postgres/00001_init_schema.down.sql diff --git a/migrations/postgres/00001_init_schema.sql b/tools/migrate/migrate/sql/postgres/00001_init_schema.up.sql similarity index 99% rename from migrations/postgres/00001_init_schema.sql rename to tools/migrate/migrate/sql/postgres/00001_init_schema.up.sql index 5523cde7..0a4d3d96 100644 --- a/migrations/postgres/00001_init_schema.sql +++ b/tools/migrate/migrate/sql/postgres/00001_init_schema.up.sql @@ -1,3 +1,4 @@ +-- 000001_init_schema.up.sql CREATE TABLE IF NOT EXISTS "ads" ( "id" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, @@ -455,4 +456,3 @@ CREATE TABLE IF NOT EXISTS "server_rule_group" PRIMARY KEY ("id"), CONSTRAINT "unique_name" UNIQUE ("name") ); - diff --git a/migrations/postgres/00002_init_basic_data.down.sql b/tools/migrate/migrate/sql/postgres/00002_init_basic_data.down.sql similarity index 100% rename from migrations/postgres/00002_init_basic_data.down.sql rename to tools/migrate/migrate/sql/postgres/00002_init_basic_data.down.sql diff --git a/migrations/postgres/00002_init_basic_data.sql b/tools/migrate/migrate/sql/postgres/00002_init_basic_data.up.sql similarity index 99% rename from migrations/postgres/00002_init_basic_data.sql rename to tools/migrate/migrate/sql/postgres/00002_init_basic_data.up.sql index fae085a9..08c01814 100644 --- a/migrations/postgres/00002_init_basic_data.sql +++ b/tools/migrate/migrate/sql/postgres/00002_init_basic_data.up.sql @@ -1,3 +1,4 @@ +-- 000002_init_data.up.sql -- auth_method INSERT INTO "auth_method" ("id", "method", "config", "enabled", "created_at", "updated_at") VALUES @@ -113,4 +114,3 @@ VALUES (1, 'site', 'SiteLogo', '/favicon.svg', 'string', 'Site Logo', '2025-04-2 SELECT setval(pg_get_serial_sequence('"auth_method"', 'id'), COALESCE((SELECT MAX("id") FROM "auth_method"), 1), true); SELECT setval(pg_get_serial_sequence('"subscribe_type"', 'id'), COALESCE((SELECT MAX("id") FROM "subscribe_type"), 1), true); SELECT setval(pg_get_serial_sequence('"system"', 'id'), COALESCE((SELECT MAX("id") FROM "system"), 1), true); - diff --git a/migrations/postgres/02003_update_payment.down.sql b/tools/migrate/migrate/sql/postgres/02003_update_payment.down.sql similarity index 100% rename from migrations/postgres/02003_update_payment.down.sql rename to tools/migrate/migrate/sql/postgres/02003_update_payment.down.sql diff --git a/migrations/postgres/02003_update_payment.sql b/tools/migrate/migrate/sql/postgres/02003_update_payment.up.sql similarity index 99% rename from migrations/postgres/02003_update_payment.sql rename to tools/migrate/migrate/sql/postgres/02003_update_payment.up.sql index c757703f..d692e5e2 100644 --- a/migrations/postgres/02003_update_payment.sql +++ b/tools/migrate/migrate/sql/postgres/02003_update_payment.up.sql @@ -4,4 +4,3 @@ ALTER TABLE "payment" ADD COLUMN IF NOT EXISTS "platform" VARCHAR(100) NOT NULL ALTER TABLE "payment" DROP COLUMN IF EXISTS "mark"; ALTER TABLE "payment" ADD COLUMN IF NOT EXISTS "description" TEXT; ALTER TABLE "payment" ADD COLUMN IF NOT EXISTS "token" VARCHAR(255) DEFAULT NULL; - diff --git a/migrations/postgres/02004_rebuild_rule.down.sql b/tools/migrate/migrate/sql/postgres/02004_rebuild_rule.down.sql similarity index 100% rename from migrations/postgres/02004_rebuild_rule.down.sql rename to tools/migrate/migrate/sql/postgres/02004_rebuild_rule.down.sql diff --git a/migrations/postgres/02004_rebuild_rule.sql b/tools/migrate/migrate/sql/postgres/02004_rebuild_rule.up.sql similarity index 99% rename from migrations/postgres/02004_rebuild_rule.sql rename to tools/migrate/migrate/sql/postgres/02004_rebuild_rule.up.sql index 0812765d..c0504d2b 100644 --- a/migrations/postgres/02004_rebuild_rule.sql +++ b/tools/migrate/migrate/sql/postgres/02004_rebuild_rule.up.sql @@ -17,4 +17,3 @@ CREATE TABLE "server_rule_group" CONSTRAINT "uni_server_rule_group_name" UNIQUE ("name") ); CREATE INDEX IF NOT EXISTS "server_rule_group_idx_enable" ON "server_rule_group" ("enable"); - diff --git a/migrations/postgres/02005_device_online_record.down.sql b/tools/migrate/migrate/sql/postgres/02005_device_online_record.down.sql similarity index 100% rename from migrations/postgres/02005_device_online_record.down.sql rename to tools/migrate/migrate/sql/postgres/02005_device_online_record.down.sql diff --git a/migrations/postgres/02005_device_online_record.sql b/tools/migrate/migrate/sql/postgres/02005_device_online_record.up.sql similarity index 99% rename from migrations/postgres/02005_device_online_record.sql rename to tools/migrate/migrate/sql/postgres/02005_device_online_record.up.sql index 3668fa3d..13792776 100644 --- a/migrations/postgres/02005_device_online_record.sql +++ b/tools/migrate/migrate/sql/postgres/02005_device_online_record.up.sql @@ -13,4 +13,3 @@ CREATE TABLE IF NOT EXISTS "user_device_online_record" ALTER TABLE "user_subscribe" ADD COLUMN IF NOT EXISTS "finished_at" TIMESTAMP NULL; ALTER TABLE "application_config" ADD COLUMN IF NOT EXISTS "invitation_link" TEXT NULL DEFAULT NULL; ALTER TABLE "application_config" ADD COLUMN IF NOT EXISTS "kr_website_id" VARCHAR(255) NULL DEFAULT NULL; - diff --git a/migrations/postgres/02006_reset_subscribe_record.down.sql b/tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.down.sql similarity index 100% rename from migrations/postgres/02006_reset_subscribe_record.down.sql rename to tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.down.sql diff --git a/migrations/postgres/02006_reset_subscribe_record.sql b/tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.up.sql similarity index 99% rename from migrations/postgres/02006_reset_subscribe_record.sql rename to tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.up.sql index c68a41a5..61b505e5 100644 --- a/migrations/postgres/02006_reset_subscribe_record.sql +++ b/tools/migrate/migrate/sql/postgres/02006_reset_subscribe_record.up.sql @@ -13,4 +13,3 @@ CREATE TABLE IF NOT EXISTS "user_reset_subscribe_log" ); CREATE INDEX IF NOT EXISTS "user_reset_subscribe_log_idx_user_id" ON "user_reset_subscribe_log" ("user_id"); CREATE INDEX IF NOT EXISTS "user_reset_subscribe_log_idx_user_subscribe_id" ON "user_reset_subscribe_log" ("user_subscribe_id"); - diff --git a/migrations/postgres/02007_adapte_rule.down.sql b/tools/migrate/migrate/sql/postgres/02007_adapte_rule.down.sql similarity index 100% rename from migrations/postgres/02007_adapte_rule.down.sql rename to tools/migrate/migrate/sql/postgres/02007_adapte_rule.down.sql diff --git a/migrations/postgres/02007_adapte_rule.sql b/tools/migrate/migrate/sql/postgres/02007_adapte_rule.up.sql similarity index 99% rename from migrations/postgres/02007_adapte_rule.sql rename to tools/migrate/migrate/sql/postgres/02007_adapte_rule.up.sql index 7517ae8e..f5e4257d 100644 --- a/migrations/postgres/02007_adapte_rule.sql +++ b/tools/migrate/migrate/sql/postgres/02007_adapte_rule.up.sql @@ -1,4 +1,3 @@ ALTER TABLE "server_rule_group" ADD COLUMN "default" BOOLEAN NOT NULL DEFAULT false, ADD COLUMN "type" VARCHAR(100) NOT NULL DEFAULT ''; - diff --git a/migrations/postgres/02100_task.down.sql b/tools/migrate/migrate/sql/postgres/02100_task.down.sql similarity index 100% rename from migrations/postgres/02100_task.down.sql rename to tools/migrate/migrate/sql/postgres/02100_task.down.sql diff --git a/migrations/postgres/02100_task.sql b/tools/migrate/migrate/sql/postgres/02100_task.up.sql similarity index 99% rename from migrations/postgres/02100_task.sql rename to tools/migrate/migrate/sql/postgres/02100_task.up.sql index 73f0c191..f24479c9 100644 --- a/migrations/postgres/02100_task.sql +++ b/tools/migrate/migrate/sql/postgres/02100_task.up.sql @@ -19,4 +19,3 @@ CREATE TABLE "email_task" ( "updated_at" TIMESTAMP(3) DEFAULT NULL, PRIMARY KEY ("id") ); - diff --git a/migrations/postgres/02101_subscribe_application.down.sql b/tools/migrate/migrate/sql/postgres/02101_subscribe_application.down.sql similarity index 100% rename from migrations/postgres/02101_subscribe_application.down.sql rename to tools/migrate/migrate/sql/postgres/02101_subscribe_application.down.sql diff --git a/migrations/postgres/02101_subscribe_application.sql b/tools/migrate/migrate/sql/postgres/02101_subscribe_application.up.sql similarity index 99% rename from migrations/postgres/02101_subscribe_application.sql rename to tools/migrate/migrate/sql/postgres/02101_subscribe_application.up.sql index 40272b1d..fd440ff6 100644 --- a/migrations/postgres/02101_subscribe_application.sql +++ b/tools/migrate/migrate/sql/postgres/02101_subscribe_application.up.sql @@ -30,4 +30,3 @@ INSERT INTO "subscribe_application" ("id", "name", "icon", "description", "schem (5, 'Surge', '', '', 'surge:///install-config?url=${encodeURIComponent(url)}', 'Surge', false, '{{- $GiB := 1073741824.0 -}}\n{{- $used := printf \"%.2f\" (divf (add (.UserInfo.Download | default 0 | float64) (.UserInfo.Upload | default 0 | float64)) $GiB) -}}\n{{- $traffic := (.UserInfo.Traffic | default 0 | float64) -}}\n{{- $total := printf \"%.2f\" (divf $traffic $GiB) -}}\n\n{{- $ExpiredAt := \"\" -}}\n{{- $expStr := printf \"%v\" .UserInfo.ExpiredAt -}}\n{{- if regexMatch `^[0-9]+$` $expStr -}}\n {{- $ts := $expStr | float64 -}}\n {{- $sec := ternary (divf $ts 1000.0) $ts (ge (len $expStr) 13) -}}\n {{- $ExpiredAt = (date \"2006-01-02 15:04:05\" (unixEpoch ($sec | int64))) -}}\n{{- else -}}\n {{- $ExpiredAt = $expStr -}}\n{{- end -}}\n\n{{- $sortFields := list \"Sort\" \"Port\" \"Name\" -}}\n{{- $sortConfig := dict \"Sort\" \"asc\" \"Port\" \"asc\" \"Name\" \"asc\" -}}\n{{- $byKey := dict -}}\n{{- range $p := .Proxies -}}\n {{- $keyParts := list -}}\n {{- range $field := $sortFields -}}\n {{- $order := index $sortConfig $field -}}\n {{- $val := default \"\" (printf \"%v\" (index $p $field)) -}}\n {{- if or (eq $field \"Sort\") (eq $field \"Port\") -}}\n {{- $val = printf \"%08d\" (int (default 0 (index $p $field))) -}}\n {{- end -}}\n {{- if eq $order \"desc\" -}}\n {{- $val = printf \"~%s\" $val -}}\n {{- end -}}\n {{- $keyParts = append $keyParts $val -}}\n {{- end -}}\n {{- $_ := set $byKey (join \"|\" $keyParts) $p -}}\n{{- end -}}\n{{- $sorted := list -}}\n{{- range $k := sortAlpha (keys $byKey) -}}\n {{- $sorted = append $sorted (index $byKey $k) -}}\n{{- end -}}\n\n{{- $supportSet := dict \"shadowsocks\" true \"vmess\" true \"vless\" true \"trojan\" true \"hysteria2\" true \"hysteria\" true \"tuic\" true \"wireguard\" true -}}\n{{- $supportedProxies := list -}}\n{{- range $proxy := $sorted -}}\n {{- if hasKey $supportSet $proxy.Type -}}\n {{- $supportedProxies = append $supportedProxies $proxy -}}\n {{- end -}}\n{{- end -}}\n\n{{- $proxyNames := \"\" -}}\n{{- range $proxy := $supportedProxies -}}\n {{- if eq $proxyNames \"\" -}}\n {{- $proxyNames = $proxy.Name -}}\n {{- else -}}\n {{- $proxyNames = printf \"%s, %s\" $proxyNames $proxy.Name -}}\n {{- end -}}\n{{- end -}}\n\n# {{ .SiteName }}-{{ .SubscribeName }}\n# Traffic: {{ $used }} GiB/{{ $total }} GiB | Expires: {{ $ExpiredAt }}\n# Generated at: {{ now | date \"2006-01-02 15:04:05\" }}\n\n#!MANAGED-CONFIG {{ .UserInfo.SubscribeURL }} interval=86400 strict=true\n\n[General]\n# 日志级别\nloglevel = notify\n\n# 外部控制器访问\nexternal-controller-access = perlnk@0.0.0.0:6170\n\n# 网络设置\nexclude-simple-hostnames = true\nshow-error-page-for-reject = true\nudp-priority = true\nudp-policy-not-supported-behaviour = reject\nipv6 = true\nipv6-vif = auto\n\n# 连接测试\nproxy-test-url = http://www.gstatic.com/generate_204\ninternet-test-url = http://www.gstatic.com/generate_204\ntest-timeout = 5\n\n# DNS 设置\ndns-server = system, 119.29.29.29, 223.5.5.5\nencrypted-dns-server = https://dns.alidns.com/dns-query\nhijack-dns = 8.8.8.8:53, 8.8.4.4:53, 1.1.1.1:53, 1.0.0.1:53\n\n# 跳过代理\nskip-proxy = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.0/8, localhost, *.local\n\n# 真实 IP\nalways-real-ip = *.lan, lens.l.google.com, *.srv.nintendo.net, *.stun.playstation.net, *.xboxlive.com, xbox.*.*.microsoft.com, *.msftncsi.com, *.msftconnecttest.com\n\n# Surge Mac 参数\nhttp-listen = 0.0.0.0:6088\nsocks5-listen = 0.0.0.0:6089\n\n# Surge iOS 参数(WiFi 共享)\nallow-wifi-access = true\nallow-hotspot-access = true\nwifi-access-http-port = 6088\nwifi-access-socks5-port = 6089\n\n[Panel]\nSubscribeInfo = title={{ .SiteName }} - {{ .SubscribeName }}, content=已用流量: {{ $used }} GiB/{{ $total }} GiB \\n到期时间: {{ $ExpiredAt}}, style=info\n\n[Proxy]\n{{- range $proxy := $supportedProxies }}\n {{- $common := \"udp-relay=true, tfo=true\" -}}\n\n {{- $server := $proxy.Server -}}\n {{- if and (contains $server \":\") (not (hasPrefix \"[\" $server)) -}}\n {{- $server = printf \"[%s]\" $server -}}\n {{- end -}}\n\n {{- $password := $.UserInfo.Password -}}\n {{- if and (eq $proxy.Type \"shadowsocks\") (ne (default \"\" $proxy.ServerKey) \"\") -}}\n {{- $method := $proxy.Method -}}\n {{- if or (hasPrefix \"2022-blake3-\" $method) (eq $method \"2022-blake3-aes-128-gcm\") (eq $method \"2022-blake3-aes-256-gcm\") -}}\n {{- $userKeyLen := ternary 16 32 (hasSuffix \"128-gcm\" $method) -}}\n {{- $pwdStr := printf \"%s\" $password -}}\n {{- $userKey := ternary $pwdStr (trunc $userKeyLen $pwdStr) (le (len $pwdStr) $userKeyLen) -}}\n {{- $serverB64 := b64enc $proxy.ServerKey -}}\n {{- $userB64 := b64enc $userKey -}}\n {{- $password = printf \"%s:%s\" $serverB64 $userB64 -}}\n {{- end -}}\n {{- end -}}\n\n {{- $SkipVerify := $proxy.AllowInsecure -}}\n\n {{- if eq $proxy.Type \"shadowsocks\" }}\n{{ $proxy.Name }} = ss, {{ $server }}, {{ $proxy.Port }}, encrypt-method={{ default \"aes-128-gcm\" $proxy.Method }}, password={{ $password }}{{- if ne (default \"\" $proxy.Obfs) \"\" }}, obfs={{ $proxy.Obfs }}{{- if ne (default \"\" $proxy.ObfsHost) \"\" }}, obfs-host={{ $proxy.ObfsHost }}{{- end }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vmess\" }}\n{{ $proxy.Name }} = vmess, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if or (eq $proxy.Security \"tls\") (eq $proxy.Security \"reality\") }}, tls=true{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"vless\" }}\n{{ $proxy.Name }} = vless, {{ $server }}, {{ $proxy.Port }}, username={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Flow) \"none\" }}, flow={{ $proxy.Flow }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"trojan\" }}\n{{ $proxy.Name }} = trojan, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if or (eq $proxy.Transport \"ws\") (eq $proxy.Transport \"websocket\") }}, ws=true{{- if ne (default \"\" $proxy.Path) \"\" }}, ws-path={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.Host) \"\" }}, ws-headers=\"Host:{{ $proxy.Host }}\"{{- end }}{{- else if eq $proxy.Transport \"grpc\" }}, grpc=true{{- if ne (default \"\" $proxy.ServiceName) \"\" }}, grpc-service-name={{ $proxy.ServiceName }}{{- end }}{{- end }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.Fingerprint) \"\" }}, fingerprint={{ $proxy.Fingerprint }}{{- end }}, {{ $common }}\n {{- else if or (eq $proxy.Type \"hysteria2\") (eq $proxy.Type \"hysteria\") }}\n{{ $proxy.Name }} = hysteria2, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if ne (default \"\" $proxy.ObfsPassword) \"\" }}, obfs=salamander, obfs-password={{ $proxy.ObfsPassword }}{{- end }}{{- if ne (default \"\" $proxy.HopPorts) \"\" }}, ports={{ $proxy.HopPorts }}{{- end }}{{- if ne (default 0 $proxy.HopInterval) 0 }}, hop-interval={{ $proxy.HopInterval }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"tuic\" }}\n{{ $proxy.Name }} = tuic, {{ $server }}, {{ $proxy.Port }}, uuid={{ default \"\" $proxy.ServerKey }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}{{- if $proxy.DisableSNI }}, disable-sni=true{{- end }}{{- if $proxy.ReduceRtt }}, reduce-rtt=true{{- end }}{{- if ne (default \"\" $proxy.UDPRelayMode) \"\" }}, udp-relay-mode={{ $proxy.UDPRelayMode }}{{- end }}{{- if ne (default \"\" $proxy.CongestionController) \"\" }}, congestion-controller={{ $proxy.CongestionController }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"wireguard\" }}\n{{ $proxy.Name }} = wireguard, {{ $server }}, {{ $proxy.Port }}, private-key={{ default \"\" $proxy.ServerKey }}, public-key={{ default \"\" $proxy.RealityPublicKey }}{{- if ne (default \"\" $proxy.Path) \"\" }}, preshared-key={{ $proxy.Path }}{{- end }}{{- if ne (default \"\" $proxy.RealityServerAddr) \"\" }}, ip={{ $proxy.RealityServerAddr }}{{- end }}{{- if ne (default 0 $proxy.RealityServerPort) 0 }}, ipv6={{ $proxy.RealityServerPort }}{{- end }}, {{ $common }}\n {{- else if eq $proxy.Type \"anytls\" }}\n{{ $proxy.Name }} = anytls, {{ $server }}, {{ $proxy.Port }}, password={{ $password }}{{- if ne (default \"\" $proxy.SNI) \"\" }}, sni={{ $proxy.SNI }}{{- end }}{{- if $proxy.AllowInsecure }}, skip-cert-verify=true{{- end }}, {{ $common }}\n {{- else }}\n{{ $proxy.Name }} = {{ $proxy.Type }}, {{ $server }}, {{ $proxy.Port }}, {{ $common }}\n {{- end }}\n{{- end }}\n\n[Proxy Group]\n# 主要策略组\n🚀 Proxy = select, 🌏 Auto, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🍎 Apple = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🔍 Google = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪟 Microsoft = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📺 GlobalMedia = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🤖 AI = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🪙 Crypto = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🎮 Game = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n📟 Telegram = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n🇨🇳 China = select, 🎯 Direct, 🚀 Proxy, include-other-group=🇺🇳 Nodes\n🐠 Final = select, 🚀 Proxy, 🎯 Direct, include-other-group=🇺🇳 Nodes\n\n# 智能选择和节点组\n🌏 Auto = smart, include-other-group=🇺🇳 Nodes\n🎯 Direct = select, DIRECT, hidden=1\n🇺🇳 Nodes = select, {{ $proxyNames }}, hidden=1\n\n[Rule]\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Apple/Apple_All.list, 🍎 Apple\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Google/Google.list, 🔍 Google\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/GitHub/GitHub.list, 🪟 Microsoft\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Microsoft/Microsoft.list, 🪟 Microsoft\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/HBO/HBO.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Disney/Disney.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/TikTok/TikTok.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Netflix/Netflix.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/GlobalMedia/GlobalMedia_All_No_Resolve.list, 📺 GlobalMedia\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Telegram/Telegram.list, 📟 Telegram\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/OpenAI/OpenAI.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Gemini/Gemini.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Copilot/Copilot.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Claude/Claude.list, 🤖 AI\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Crypto/Crypto.list, 🪙 Crypto\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Cryptocurrency/Cryptocurrency.list, 🪙 Crypto\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Game/Game.list, 🎮 Game\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Global/Global_All_No_Resolve.list, 🚀 Proxy\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/ChinaMax/ChinaMax_All_No_Resolve.list, 🇨🇳 China\nRULE-SET, https://cdn.jsdmirror.com/gh/perfect-panel/rules/rule/Surge/Lan/Lan.list, 🎯 Direct\n\nGEOIP, CN, 🇨🇳 China\nFINAL, 🐠 Final, dns-failed\n\n[URL Rewrite]\n^https?:\\/\\/(www.)?g\\.cn https://www.google.com 302\n^https?:\\/\\/(www.)?google\\.cn https://www.google.com 302\n', 'conf', '{}', '2025-08-13 00:12:37.809', '2025-08-15 22:00:50.528') ON CONFLICT DO NOTHING; COMMIT; SELECT setval(pg_get_serial_sequence('"subscribe_application"', 'id'), COALESCE((SELECT MAX("id") FROM "subscribe_application"), 1), true); - diff --git a/migrations/postgres/02102_subscribe_config.down.sql b/tools/migrate/migrate/sql/postgres/02102_subscribe_config.down.sql similarity index 100% rename from migrations/postgres/02102_subscribe_config.down.sql rename to tools/migrate/migrate/sql/postgres/02102_subscribe_config.down.sql diff --git a/migrations/postgres/02102_subscribe_config.sql b/tools/migrate/migrate/sql/postgres/02102_subscribe_config.up.sql similarity index 99% rename from migrations/postgres/02102_subscribe_config.sql rename to tools/migrate/migrate/sql/postgres/02102_subscribe_config.up.sql index 9d61f530..5a7141d0 100644 --- a/migrations/postgres/02102_subscribe_config.sql +++ b/tools/migrate/migrate/sql/postgres/02102_subscribe_config.up.sql @@ -3,4 +3,3 @@ VALUES (42, 'subscribe', 'UserAgentLimit', 'false', 'bool', 'User Agent Limit', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), (43, 'subscribe', 'UserAgentList', '', 'string', 'User Agent List', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637') ON CONFLICT DO NOTHING; SELECT setval(pg_get_serial_sequence('"system"', 'id'), COALESCE((SELECT MAX("id") FROM "system"), 1), true); - diff --git a/migrations/postgres/02103_delete_application.down.sql b/tools/migrate/migrate/sql/postgres/02103_delete_application.down.sql similarity index 100% rename from migrations/postgres/02103_delete_application.down.sql rename to tools/migrate/migrate/sql/postgres/02103_delete_application.down.sql diff --git a/migrations/postgres/02103_delete_application.sql b/tools/migrate/migrate/sql/postgres/02103_delete_application.up.sql similarity index 99% rename from migrations/postgres/02103_delete_application.sql rename to tools/migrate/migrate/sql/postgres/02103_delete_application.up.sql index 8d01aaeb..c2090166 100644 --- a/migrations/postgres/02103_delete_application.sql +++ b/tools/migrate/migrate/sql/postgres/02103_delete_application.up.sql @@ -1,4 +1,3 @@ DROP TABLE IF EXISTS "application"; DROP TABLE IF EXISTS "application_version"; DROP TABLE IF EXISTS "application_config"; - diff --git a/migrations/postgres/02104_system_log.down.sql b/tools/migrate/migrate/sql/postgres/02104_system_log.down.sql similarity index 100% rename from migrations/postgres/02104_system_log.down.sql rename to tools/migrate/migrate/sql/postgres/02104_system_log.down.sql diff --git a/migrations/postgres/02104_system_log.sql b/tools/migrate/migrate/sql/postgres/02104_system_log.up.sql similarity index 99% rename from migrations/postgres/02104_system_log.sql rename to tools/migrate/migrate/sql/postgres/02104_system_log.up.sql index 774c47af..26fd64e5 100644 --- a/migrations/postgres/02104_system_log.sql +++ b/tools/migrate/migrate/sql/postgres/02104_system_log.up.sql @@ -17,4 +17,3 @@ CREATE TABLE "system_logs" ( ); CREATE INDEX IF NOT EXISTS "system_logs_idx_type" ON "system_logs" ("type"); CREATE INDEX IF NOT EXISTS "system_logs_idx_object_id" ON "system_logs" ("object_id"); - diff --git a/migrations/postgres/02105_node.down.sql b/tools/migrate/migrate/sql/postgres/02105_node.down.sql similarity index 100% rename from migrations/postgres/02105_node.down.sql rename to tools/migrate/migrate/sql/postgres/02105_node.down.sql diff --git a/migrations/postgres/02105_node.sql b/tools/migrate/migrate/sql/postgres/02105_node.up.sql similarity index 99% rename from migrations/postgres/02105_node.sql rename to tools/migrate/migrate/sql/postgres/02105_node.up.sql index faf33301..9f12d93b 100644 --- a/migrations/postgres/02105_node.sql +++ b/tools/migrate/migrate/sql/postgres/02105_node.up.sql @@ -25,4 +25,3 @@ CREATE TABLE IF NOT EXISTS "nodes" ( "updated_at" TIMESTAMP(3) DEFAULT NULL, PRIMARY KEY ("id") ); - diff --git a/migrations/postgres/02106_subscribe.down.sql b/tools/migrate/migrate/sql/postgres/02106_subscribe.down.sql similarity index 100% rename from migrations/postgres/02106_subscribe.down.sql rename to tools/migrate/migrate/sql/postgres/02106_subscribe.down.sql diff --git a/migrations/postgres/02106_subscribe.sql b/tools/migrate/migrate/sql/postgres/02106_subscribe.up.sql similarity index 99% rename from migrations/postgres/02106_subscribe.sql rename to tools/migrate/migrate/sql/postgres/02106_subscribe.up.sql index 36e5847e..10934281 100644 --- a/migrations/postgres/02106_subscribe.sql +++ b/tools/migrate/migrate/sql/postgres/02106_subscribe.up.sql @@ -4,4 +4,3 @@ ADD COLUMN "node_tags" VARCHAR(255) NOT NULL DEFAULT '' , DROP COLUMN "server", DROP COLUMN "server_group"; DROP TABLE IF EXISTS "server_rule_group"; - diff --git a/migrations/postgres/02107_log_setting.down.sql b/tools/migrate/migrate/sql/postgres/02107_log_setting.down.sql similarity index 100% rename from migrations/postgres/02107_log_setting.down.sql rename to tools/migrate/migrate/sql/postgres/02107_log_setting.down.sql diff --git a/migrations/postgres/02107_log_setting.sql b/tools/migrate/migrate/sql/postgres/02107_log_setting.up.sql similarity index 99% rename from migrations/postgres/02107_log_setting.sql rename to tools/migrate/migrate/sql/postgres/02107_log_setting.up.sql index e332535d..438f29c3 100644 --- a/migrations/postgres/02107_log_setting.sql +++ b/tools/migrate/migrate/sql/postgres/02107_log_setting.up.sql @@ -2,4 +2,3 @@ INSERT INTO "system" ("category", "key", "value", "type", "desc", "created_at", VALUES ('log', 'AutoClear', 'true', 'bool', 'Auto Clear Log', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), ('log', 'ClearDays', '7', 'int', 'Clear Days', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637') ON CONFLICT DO NOTHING; - diff --git a/migrations/postgres/02108_user_referral.down.sql b/tools/migrate/migrate/sql/postgres/02108_user_referral.down.sql similarity index 100% rename from migrations/postgres/02108_user_referral.down.sql rename to tools/migrate/migrate/sql/postgres/02108_user_referral.down.sql diff --git a/migrations/postgres/02108_user_referral.sql b/tools/migrate/migrate/sql/postgres/02108_user_referral.up.sql similarity index 99% rename from migrations/postgres/02108_user_referral.sql rename to tools/migrate/migrate/sql/postgres/02108_user_referral.up.sql index 28211db7..c9709159 100644 --- a/migrations/postgres/02108_user_referral.sql +++ b/tools/migrate/migrate/sql/postgres/02108_user_referral.up.sql @@ -1,4 +1,3 @@ ALTER TABLE "user" ADD COLUMN "referral_percentage" SMALLINT NOT NULL DEFAULT 0, ADD COLUMN "only_first_purchase" BOOLEAN NOT NULL DEFAULT true; - diff --git a/migrations/postgres/02109_node_sort.down.sql b/tools/migrate/migrate/sql/postgres/02109_node_sort.down.sql similarity index 100% rename from migrations/postgres/02109_node_sort.down.sql rename to tools/migrate/migrate/sql/postgres/02109_node_sort.down.sql diff --git a/migrations/postgres/02109_node_sort.sql b/tools/migrate/migrate/sql/postgres/02109_node_sort.up.sql similarity index 98% rename from migrations/postgres/02109_node_sort.sql rename to tools/migrate/migrate/sql/postgres/02109_node_sort.up.sql index aa4a1ee5..aba6f76c 100644 --- a/migrations/postgres/02109_node_sort.sql +++ b/tools/migrate/migrate/sql/postgres/02109_node_sort.up.sql @@ -1,3 +1,2 @@ ALTER TABLE "nodes" ADD COLUMN "sort" INTEGER NOT NULL DEFAULT 0; - diff --git a/migrations/postgres/02110_traffic_log_index.down.sql b/tools/migrate/migrate/sql/postgres/02110_traffic_log_index.down.sql similarity index 100% rename from migrations/postgres/02110_traffic_log_index.down.sql rename to tools/migrate/migrate/sql/postgres/02110_traffic_log_index.down.sql diff --git a/migrations/postgres/02110_traffic_log_index.sql b/tools/migrate/migrate/sql/postgres/02110_traffic_log_index.up.sql similarity index 98% rename from migrations/postgres/02110_traffic_log_index.sql rename to tools/migrate/migrate/sql/postgres/02110_traffic_log_index.up.sql index 411378c5..2cf61f24 100644 --- a/migrations/postgres/02110_traffic_log_index.sql +++ b/tools/migrate/migrate/sql/postgres/02110_traffic_log_index.up.sql @@ -1,2 +1 @@ CREATE INDEX idx_traffic_log_time_user_sub ON traffic_log (timestamp, user_id, subscribe_id); - diff --git a/migrations/postgres/02111_clear_table.down.sql b/tools/migrate/migrate/sql/postgres/02111_clear_table.down.sql similarity index 100% rename from migrations/postgres/02111_clear_table.down.sql rename to tools/migrate/migrate/sql/postgres/02111_clear_table.down.sql diff --git a/migrations/postgres/02111_clear_table.sql b/tools/migrate/migrate/sql/postgres/02111_clear_table.up.sql similarity index 98% rename from migrations/postgres/02111_clear_table.sql rename to tools/migrate/migrate/sql/postgres/02111_clear_table.up.sql index 60c10e1e..92562a53 100644 --- a/migrations/postgres/02111_clear_table.sql +++ b/tools/migrate/migrate/sql/postgres/02111_clear_table.up.sql @@ -1,3 +1,2 @@ DROP TABLE IF EXISTS "subscribe_type"; DROP TABLE IF EXISTS "sms"; - diff --git a/migrations/postgres/02112_subscribe.down.sql b/tools/migrate/migrate/sql/postgres/02112_subscribe.down.sql similarity index 100% rename from migrations/postgres/02112_subscribe.down.sql rename to tools/migrate/migrate/sql/postgres/02112_subscribe.down.sql diff --git a/migrations/postgres/02112_subscribe.sql b/tools/migrate/migrate/sql/postgres/02112_subscribe.up.sql similarity index 99% rename from migrations/postgres/02112_subscribe.sql rename to tools/migrate/migrate/sql/postgres/02112_subscribe.up.sql index 22caadd2..257c4d1a 100644 --- a/migrations/postgres/02112_subscribe.sql +++ b/tools/migrate/migrate/sql/postgres/02112_subscribe.up.sql @@ -2,4 +2,3 @@ ALTER TABLE "subscribe" DROP COLUMN "group_id", ADD COLUMN "language" VARCHAR(255) NOT NULL DEFAULT ''; DROP TABLE IF EXISTS "subscribe_group"; - diff --git a/migrations/postgres/02113_task.down.sql b/tools/migrate/migrate/sql/postgres/02113_task.down.sql similarity index 100% rename from migrations/postgres/02113_task.down.sql rename to tools/migrate/migrate/sql/postgres/02113_task.down.sql diff --git a/migrations/postgres/02113_task.sql b/tools/migrate/migrate/sql/postgres/02113_task.up.sql similarity index 99% rename from migrations/postgres/02113_task.sql rename to tools/migrate/migrate/sql/postgres/02113_task.up.sql index a87af3bb..1e6f3a55 100644 --- a/migrations/postgres/02113_task.sql +++ b/tools/migrate/migrate/sql/postgres/02113_task.up.sql @@ -12,4 +12,3 @@ CREATE TABLE "task" ( "updated_at" TIMESTAMP(3) DEFAULT NULL, PRIMARY KEY ("id") ); - diff --git a/migrations/postgres/02114_node_config.down.sql b/tools/migrate/migrate/sql/postgres/02114_node_config.down.sql similarity index 100% rename from migrations/postgres/02114_node_config.down.sql rename to tools/migrate/migrate/sql/postgres/02114_node_config.down.sql diff --git a/migrations/postgres/02114_node_config.sql b/tools/migrate/migrate/sql/postgres/02114_node_config.up.sql similarity index 99% rename from migrations/postgres/02114_node_config.sql rename to tools/migrate/migrate/sql/postgres/02114_node_config.up.sql index b91c98cf..2b228332 100644 --- a/migrations/postgres/02114_node_config.sql +++ b/tools/migrate/migrate/sql/postgres/02114_node_config.up.sql @@ -4,4 +4,3 @@ VALUES ('server', 'TrafficReportThreshold', '0', 'int', 'Traffic report threshol ('server', 'DNS', '', 'string', 'DNS', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), ('server', 'Block', '', 'string', 'Block', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'), ('server', 'Outbound', '', 'string', 'Proxy Outbound', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637') ON CONFLICT DO NOTHING; - diff --git a/migrations/postgres/02115_ads.down.sql b/tools/migrate/migrate/sql/postgres/02115_ads.down.sql similarity index 100% rename from migrations/postgres/02115_ads.down.sql rename to tools/migrate/migrate/sql/postgres/02115_ads.down.sql diff --git a/migrations/postgres/02115_ads.sql b/tools/migrate/migrate/sql/postgres/02115_ads.up.sql similarity index 98% rename from migrations/postgres/02115_ads.sql rename to tools/migrate/migrate/sql/postgres/02115_ads.up.sql index 6a45cde6..89d6d7d0 100644 --- a/migrations/postgres/02115_ads.sql +++ b/tools/migrate/migrate/sql/postgres/02115_ads.up.sql @@ -1,2 +1 @@ ALTER TABLE "ads" ADD COLUMN IF NOT EXISTS "description" VARCHAR(255) DEFAULT ''; - diff --git a/migrations/postgres/02116_user_algo.down.sql b/tools/migrate/migrate/sql/postgres/02116_user_algo.down.sql similarity index 100% rename from migrations/postgres/02116_user_algo.down.sql rename to tools/migrate/migrate/sql/postgres/02116_user_algo.down.sql diff --git a/migrations/postgres/02116_user_algo.sql b/tools/migrate/migrate/sql/postgres/02116_user_algo.up.sql similarity index 99% rename from migrations/postgres/02116_user_algo.sql rename to tools/migrate/migrate/sql/postgres/02116_user_algo.up.sql index 35ebc491..77f34551 100644 --- a/migrations/postgres/02116_user_algo.sql +++ b/tools/migrate/migrate/sql/postgres/02116_user_algo.up.sql @@ -1,3 +1,2 @@ ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "algo" VARCHAR(20) NOT NULL DEFAULT 'default'; ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "salt" VARCHAR(20) NOT NULL DEFAULT 'default'; - diff --git a/migrations/postgres/02117_site_custom_data.down.sql b/tools/migrate/migrate/sql/postgres/02117_site_custom_data.down.sql similarity index 100% rename from migrations/postgres/02117_site_custom_data.down.sql rename to tools/migrate/migrate/sql/postgres/02117_site_custom_data.down.sql diff --git a/migrations/postgres/02117_site_custom_data.sql b/tools/migrate/migrate/sql/postgres/02117_site_custom_data.up.sql similarity index 99% rename from migrations/postgres/02117_site_custom_data.sql rename to tools/migrate/migrate/sql/postgres/02117_site_custom_data.up.sql index 782c7198..81a70c26 100644 --- a/migrations/postgres/02117_site_custom_data.sql +++ b/tools/migrate/migrate/sql/postgres/02117_site_custom_data.up.sql @@ -5,4 +5,3 @@ SELECT 'site', 'CustomData', '{ WHERE NOT EXISTS ( SELECT 1 FROM "system" WHERE "category" = 'site' AND "key" = 'CustomData' ); - diff --git a/migrations/postgres/02118_traffic_log_idx.down.sql b/tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.down.sql similarity index 100% rename from migrations/postgres/02118_traffic_log_idx.down.sql rename to tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.down.sql diff --git a/migrations/postgres/02118_traffic_log_idx.sql b/tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.up.sql similarity index 98% rename from migrations/postgres/02118_traffic_log_idx.sql rename to tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.up.sql index 3bc66c52..d28c1346 100644 --- a/migrations/postgres/02118_traffic_log_idx.sql +++ b/tools/migrate/migrate/sql/postgres/02118_traffic_log_idx.up.sql @@ -1,2 +1 @@ CREATE INDEX IF NOT EXISTS "idx_timestamp" ON "traffic_log" ("timestamp"); - diff --git a/migrations/postgres/02119_user_subscribe_note.down.sql b/tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.down.sql similarity index 100% rename from migrations/postgres/02119_user_subscribe_note.down.sql rename to tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.down.sql diff --git a/migrations/postgres/02119_user_subscribe_note.sql b/tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.up.sql similarity index 98% rename from migrations/postgres/02119_user_subscribe_note.sql rename to tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.up.sql index 0da811fd..b8bfbfa6 100644 --- a/migrations/postgres/02119_user_subscribe_note.sql +++ b/tools/migrate/migrate/sql/postgres/02119_user_subscribe_note.up.sql @@ -1,3 +1,2 @@ ALTER TABLE "user_subscribe" ADD COLUMN "note" VARCHAR(500) NOT NULL DEFAULT ''; - diff --git a/migrations/postgres/02120_user_rules.down.sql b/tools/migrate/migrate/sql/postgres/02120_user_rules.down.sql similarity index 100% rename from migrations/postgres/02120_user_rules.down.sql rename to tools/migrate/migrate/sql/postgres/02120_user_rules.down.sql diff --git a/migrations/postgres/02120_user_rules.sql b/tools/migrate/migrate/sql/postgres/02120_user_rules.up.sql similarity index 98% rename from migrations/postgres/02120_user_rules.sql rename to tools/migrate/migrate/sql/postgres/02120_user_rules.up.sql index b61e04dc..92b8834f 100644 --- a/migrations/postgres/02120_user_rules.sql +++ b/tools/migrate/migrate/sql/postgres/02120_user_rules.up.sql @@ -1,3 +1,2 @@ ALTER TABLE "user" ADD COLUMN "rules" TEXT NULL; - diff --git a/migrations/postgres/02121_user_withdrawal.down.sql b/tools/migrate/migrate/sql/postgres/02121_user_withdrawal.down.sql similarity index 100% rename from migrations/postgres/02121_user_withdrawal.down.sql rename to tools/migrate/migrate/sql/postgres/02121_user_withdrawal.down.sql diff --git a/migrations/postgres/02121_user_withdrawal.sql b/tools/migrate/migrate/sql/postgres/02121_user_withdrawal.up.sql similarity index 99% rename from migrations/postgres/02121_user_withdrawal.sql rename to tools/migrate/migrate/sql/postgres/02121_user_withdrawal.up.sql index 1184c4c5..99c50274 100644 --- a/migrations/postgres/02121_user_withdrawal.sql +++ b/tools/migrate/migrate/sql/postgres/02121_user_withdrawal.up.sql @@ -13,4 +13,3 @@ CREATE INDEX IF NOT EXISTS "withdrawals_idx_user_id" ON "withdrawals" ("user_id" INSERT INTO "system" ("category", "key", "value", "type", "desc", "created_at", "updated_at") VALUES ('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637') ON CONFLICT DO NOTHING; - diff --git a/migrations/postgres/02122_server.down.sql b/tools/migrate/migrate/sql/postgres/02122_server.down.sql similarity index 100% rename from migrations/postgres/02122_server.down.sql rename to tools/migrate/migrate/sql/postgres/02122_server.down.sql diff --git a/migrations/postgres/02122_server.sql b/tools/migrate/migrate/sql/postgres/02122_server.up.sql similarity index 96% rename from migrations/postgres/02122_server.sql rename to tools/migrate/migrate/sql/postgres/02122_server.up.sql index 984fcb12..177b007a 100644 --- a/migrations/postgres/02122_server.sql +++ b/tools/migrate/migrate/sql/postgres/02122_server.up.sql @@ -1,2 +1 @@ DROP TABLE IF EXISTS "server"; - diff --git a/migrations/postgres/02123_subscribe_original.down.sql b/tools/migrate/migrate/sql/postgres/02123_subscribe_original.down.sql similarity index 100% rename from migrations/postgres/02123_subscribe_original.down.sql rename to tools/migrate/migrate/sql/postgres/02123_subscribe_original.down.sql diff --git a/migrations/postgres/02123_subscribe_original.sql b/tools/migrate/migrate/sql/postgres/02123_subscribe_original.up.sql similarity index 98% rename from migrations/postgres/02123_subscribe_original.sql rename to tools/migrate/migrate/sql/postgres/02123_subscribe_original.up.sql index 9a9728c2..b17c8937 100644 --- a/migrations/postgres/02123_subscribe_original.sql +++ b/tools/migrate/migrate/sql/postgres/02123_subscribe_original.up.sql @@ -1,3 +1,2 @@ ALTER TABLE "subscribe" ADD COLUMN "show_original_price" BOOLEAN NOT NULL DEFAULT false; - diff --git a/migrations/postgres/02124_server_group_delete.down.sql b/tools/migrate/migrate/sql/postgres/02124_server_group_delete.down.sql similarity index 100% rename from migrations/postgres/02124_server_group_delete.down.sql rename to tools/migrate/migrate/sql/postgres/02124_server_group_delete.down.sql diff --git a/migrations/postgres/02124_server_group_delete.sql b/tools/migrate/migrate/sql/postgres/02124_server_group_delete.up.sql similarity index 97% rename from migrations/postgres/02124_server_group_delete.sql rename to tools/migrate/migrate/sql/postgres/02124_server_group_delete.up.sql index eaeafb60..994e9cee 100644 --- a/migrations/postgres/02124_server_group_delete.sql +++ b/tools/migrate/migrate/sql/postgres/02124_server_group_delete.up.sql @@ -1,2 +1 @@ DROP TABLE IF EXISTS "server_group"; - diff --git a/migrations/postgres/02125_subscribe_stock.down.sql b/tools/migrate/migrate/sql/postgres/02125_subscribe_stock.down.sql similarity index 100% rename from migrations/postgres/02125_subscribe_stock.down.sql rename to tools/migrate/migrate/sql/postgres/02125_subscribe_stock.down.sql diff --git a/migrations/postgres/02125_subscribe_stock.sql b/tools/migrate/migrate/sql/postgres/02125_subscribe_stock.up.sql similarity index 99% rename from migrations/postgres/02125_subscribe_stock.sql rename to tools/migrate/migrate/sql/postgres/02125_subscribe_stock.up.sql index cb6b9e59..56b8db52 100644 --- a/migrations/postgres/02125_subscribe_stock.sql +++ b/tools/migrate/migrate/sql/postgres/02125_subscribe_stock.up.sql @@ -2,4 +2,3 @@ UPDATE "subscribe" SET "inventory" = -1 WHERE "inventory" = 0; - diff --git a/migrations/postgres/02126_system_log_idx.down.sql b/tools/migrate/migrate/sql/postgres/02126_system_log_idx.down.sql similarity index 100% rename from migrations/postgres/02126_system_log_idx.down.sql rename to tools/migrate/migrate/sql/postgres/02126_system_log_idx.down.sql diff --git a/migrations/postgres/02126_system_log_idx.sql b/tools/migrate/migrate/sql/postgres/02126_system_log_idx.up.sql similarity index 98% rename from migrations/postgres/02126_system_log_idx.sql rename to tools/migrate/migrate/sql/postgres/02126_system_log_idx.up.sql index eff09df8..6b2e0399 100644 --- a/migrations/postgres/02126_system_log_idx.sql +++ b/tools/migrate/migrate/sql/postgres/02126_system_log_idx.up.sql @@ -1,2 +1 @@ CREATE INDEX idx_type_date ON system_logs (type, date); - diff --git a/migrations/postgres/02127_search_indexes.down.sql b/tools/migrate/migrate/sql/postgres/02127_search_indexes.down.sql similarity index 100% rename from migrations/postgres/02127_search_indexes.down.sql rename to tools/migrate/migrate/sql/postgres/02127_search_indexes.down.sql diff --git a/migrations/postgres/02127_search_indexes.sql b/tools/migrate/migrate/sql/postgres/02127_search_indexes.up.sql similarity index 99% rename from migrations/postgres/02127_search_indexes.sql rename to tools/migrate/migrate/sql/postgres/02127_search_indexes.up.sql index db226dea..c8189c85 100644 --- a/migrations/postgres/02127_search_indexes.sql +++ b/tools/migrate/migrate/sql/postgres/02127_search_indexes.up.sql @@ -29,4 +29,3 @@ CREATE INDEX IF NOT EXISTS "idx_subscribe_description_trgm" ON "subscribe" USING CREATE INDEX IF NOT EXISTS "idx_ticket_title_trgm" ON "ticket" USING GIN ("title" gin_trgm_ops); CREATE INDEX IF NOT EXISTS "idx_ticket_description_trgm" ON "ticket" USING GIN ("description" gin_trgm_ops); CREATE INDEX IF NOT EXISTS "idx_system_logs_content_trgm" ON "system_logs" USING GIN ("content" gin_trgm_ops); - diff --git a/migrations/postgres/02128_server_config_override.down.sql b/tools/migrate/migrate/sql/postgres/02128_server_config_override.down.sql similarity index 100% rename from migrations/postgres/02128_server_config_override.down.sql rename to tools/migrate/migrate/sql/postgres/02128_server_config_override.down.sql diff --git a/migrations/postgres/02128_server_config_override.sql b/tools/migrate/migrate/sql/postgres/02128_server_config_override.up.sql similarity index 99% rename from migrations/postgres/02128_server_config_override.sql rename to tools/migrate/migrate/sql/postgres/02128_server_config_override.up.sql index 41995724..f3dade47 100644 --- a/migrations/postgres/02128_server_config_override.sql +++ b/tools/migrate/migrate/sql/postgres/02128_server_config_override.up.sql @@ -11,4 +11,3 @@ CREATE TABLE IF NOT EXISTS "server_config_overrides" PRIMARY KEY ("id"), CONSTRAINT "uni_server_config_overrides_server_id" UNIQUE ("server_id") ); - diff --git a/migrations/postgres/02129_payment_sort.down.sql b/tools/migrate/migrate/sql/postgres/02129_payment_sort.down.sql similarity index 100% rename from migrations/postgres/02129_payment_sort.down.sql rename to tools/migrate/migrate/sql/postgres/02129_payment_sort.down.sql diff --git a/migrations/postgres/02129_payment_sort.sql b/tools/migrate/migrate/sql/postgres/02129_payment_sort.up.sql similarity index 99% rename from migrations/postgres/02129_payment_sort.sql rename to tools/migrate/migrate/sql/postgres/02129_payment_sort.up.sql index 26df4811..faadf066 100644 --- a/migrations/postgres/02129_payment_sort.sql +++ b/tools/migrate/migrate/sql/postgres/02129_payment_sort.up.sql @@ -4,4 +4,3 @@ ALTER TABLE "payment" UPDATE "payment" SET "sort" = "id" WHERE "sort" = 0; - diff --git a/migrations/postgres/02130_subscribe_tutorial.down.sql b/tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.down.sql similarity index 100% rename from migrations/postgres/02130_subscribe_tutorial.down.sql rename to tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.down.sql diff --git a/migrations/postgres/02130_subscribe_tutorial.sql b/tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.up.sql similarity index 99% rename from migrations/postgres/02130_subscribe_tutorial.sql rename to tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.up.sql index d024da46..19ca2ce2 100644 --- a/migrations/postgres/02130_subscribe_tutorial.sql +++ b/tools/migrate/migrate/sql/postgres/02130_subscribe_tutorial.up.sql @@ -3,4 +3,3 @@ SELECT 'subscribe', 'ShowTutorial', 'true', 'bool', 'Show tutorial section on th WHERE NOT EXISTS ( SELECT 1 FROM "system" WHERE "category" = 'subscribe' AND "key" = 'ShowTutorial' ); - diff --git a/migrations/postgres/02131_timestamptz_last_reported_at.down.sql b/tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql similarity index 100% rename from migrations/postgres/02131_timestamptz_last_reported_at.down.sql rename to tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.down.sql diff --git a/migrations/postgres/02131_timestamptz_last_reported_at.sql b/tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql similarity index 99% rename from migrations/postgres/02131_timestamptz_last_reported_at.sql rename to tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql index 9e06fdf1..4ebe3610 100644 --- a/migrations/postgres/02131_timestamptz_last_reported_at.sql +++ b/tools/migrate/migrate/sql/postgres/02131_timestamptz_last_reported_at.up.sql @@ -1,4 +1,3 @@ ALTER TABLE "servers" ALTER COLUMN "last_reported_at" TYPE timestamptz USING "last_reported_at" AT TIME ZONE 'UTC'; - diff --git a/tools/migrate/ppanel-migrate b/tools/migrate/ppanel-migrate new file mode 100755 index 00000000..044e4443 Binary files /dev/null and b/tools/migrate/ppanel-migrate differ