merge: sync upstream/master preserving local changes

This commit is contained in:
yinjianm
2026-04-23 22:27:18 +08:00
94 changed files with 14065 additions and 650 deletions
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('v2_server_machine_load_history', function (Blueprint $table) {
$table->double('net_in_speed')->nullable()->after('disk_used');
$table->double('net_out_speed')->nullable()->after('net_in_speed');
});
}
public function down(): void
{
Schema::table('v2_server_machine_load_history', function (Blueprint $table) {
$table->dropColumn(['net_in_speed', 'net_out_speed']);
});
}
};
@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('v2_server', function (Blueprint $table) {
$table->boolean('enabled')->nullable()->default(true)->change();
});
}
public function down(): void
{
Schema::table('v2_server', function (Blueprint $table) {
$table->boolean('enabled')->default(true)->change();
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
/**
* One-shot backfill of users.next_reset_at for legacy installs.
*
* Replaces the previous `reset:traffic --force` step in `xboard:update`,
* which had to run on every container start. Now it runs exactly once per
* database (Laravel migrations are tracked).
*/
return new class extends Migration {
public function up(): void
{
if (!Schema::hasColumn('v2_user', 'next_reset_at')) {
return;
}
Artisan::call('reset:traffic', ['--fix-null' => true]);
}
public function down(): void
{
// Backfill is non-destructive; nothing to roll back.
}
};
@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
// Backfill default utls for legacy vless reality nodes after the uTLS refactor.
return new class extends Migration
{
public function up(): void
{
if (!Schema::hasTable('v2_server')) {
return;
}
DB::table('v2_server')
->where('type', 'vless')
->orderBy('id')
->chunkById(200, function ($servers) {
foreach ($servers as $server) {
$settings = json_decode($server->protocol_settings ?? '', true);
if (!is_array($settings) || (int) ($settings['tls'] ?? 0) != 2) {
continue;
}
$existing = $settings['utls'] ?? null;
if (is_array($existing) && ($existing['enabled'] ?? false) === true) {
continue;
}
$settings['utls'] = [
'enabled' => true,
'fingerprint' => is_array($existing) && !empty($existing['fingerprint'])
? $existing['fingerprint']
: 'chrome',
];
DB::table('v2_server')
->where('id', $server->id)
->update(['protocol_settings' => json_encode($settings)]);
}
});
}
public function down(): void
{
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('v2_mail_templates', function (Blueprint $table) {
$table->id();
$table->string('name', 64)->unique();
$table->string('subject', 255);
$table->longText('content');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('v2_mail_templates');
}
};
@@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Add last_reply_user_id column if not exists
if (!Schema::hasColumn('v2_ticket', 'last_reply_user_id')) {
Schema::table('v2_ticket', function (Blueprint $table) {
$table->integer('last_reply_user_id')->nullable()->after('reply_status');
});
}
// Fix reply_status semantics: swap 0 and 1
// Old: 0=admin replied, 1=user replied (inverted)
// New: 0=待回复(waiting), 1=已回复(replied) — matches frontend expectations
DB::table('v2_ticket')
->whereIn('reply_status', [0, 1])
->update([
'reply_status' => DB::raw("CASE WHEN reply_status = 0 THEN 1 WHEN reply_status = 1 THEN 0 END")
]);
// Fix default: new tickets should be "待回复" (0), not "已回复" (1)
Schema::table('v2_ticket', function (Blueprint $table) {
$table->integer('reply_status')->default(0)->comment('0:待回复 1:已回复')->change();
});
}
public function down(): void
{
// Reverse the swap
DB::table('v2_ticket')
->whereIn('reply_status', [0, 1])
->update([
'reply_status' => DB::raw("CASE WHEN reply_status = 0 THEN 1 WHEN reply_status = 1 THEN 0 END")
]);
Schema::table('v2_ticket', function (Blueprint $table) {
$table->integer('reply_status')->default(1)->comment('0:待回复 1:已回复')->change();
});
// Note: last_reply_user_id column is intentionally kept to avoid dropping
// a column that may have existed before this migration.
}
};
@@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::table('v2_server')
->where('type', 'trojan')
->chunkById(100, function ($servers) {
foreach ($servers as $server) {
$settings = json_decode($server->protocol_settings, true);
if (!$settings) continue;
$rootSni = $settings['server_name'] ?? null;
$rootInsecure = $settings['allow_insecure'] ?? false;
$tlsSettings = $settings['tls_settings'] ?? null;
$needsUpdate = false;
if (!is_array($tlsSettings)) {
if ($rootSni !== null || $rootInsecure) {
$settings['tls_settings'] = [
'server_name' => $rootSni,
'allow_insecure' => (bool) $rootInsecure,
];
$needsUpdate = true;
}
} else {
$tlsSni = $tlsSettings['server_name'] ?? null;
if (($tlsSni === null || $tlsSni === '') && $rootSni !== null && $rootSni !== '') {
$settings['tls_settings']['server_name'] = $rootSni;
$needsUpdate = true;
}
if (($tlsSettings['allow_insecure'] ?? null) === null && $rootInsecure) {
$settings['tls_settings']['allow_insecure'] = true;
$needsUpdate = true;
}
}
if ($needsUpdate) {
DB::table('v2_server')
->where('id', $server->id)
->update(['protocol_settings' => json_encode($settings)]);
}
}
});
}
public function down(): void
{
}
};