Compare commits

...

28 Commits

Author SHA1 Message Date
xboard
13756956a6 fix: reset traffic stats when copying server nodes 2026-04-11 20:24:43 +08:00
Valentin Lobstein
121511523f Fix: CVE-2026-39912 - Magic link token leak in loginWithMailLink (#873)
The loginWithMailLink endpoint returns the magic login link in the
HTTP response body, allowing unauthenticated account takeover.

The fix returns true instead of the link. The email delivery is
the authentication factor.

Bug inherited from V2Board commit bdb10bed (2022-06-27).
2026-04-10 02:44:20 +08:00
xboard
1fe6531924 fix(update): avoid duplicate safe.directory entries for repo and admin submodule 2026-04-09 20:31:19 +08:00
xboard
38ea7d0067 docs: add donation section 2026-04-09 00:21:28 +08:00
xboard
58ef46f754 fix: stop sending VLESS decryption when encryption is disabled 2026-04-08 11:05:55 +08:00
yootus
ec49ba3fd1 Loon和Surfboard适配anytls (#854)
* Loon适配anytls

* Surfboard适配anytls

Surfboard适配anytls
2026-04-02 15:47:41 +08:00
NFamou
b7c8b31a91 Merge pull request #856 from NFamou/master
支持Surfboard下发SS2022
2026-04-02 15:46:55 +08:00
xboard
f3fd40008b updata admin asset 2026-04-02 05:51:16 +08:00
Xboard
94fc5f6942 Merge pull request #841 from cedar2025/revert-755-feat/server-id-stat-user
Revert "feat: Track user traffic per node (server_id)"
2026-03-30 18:18:35 +08:00
Xboard
c5a8c836c0 Revert "feat: Track user traffic per node (server_id)" 2026-03-30 18:17:27 +08:00
xboard
048530a893 Remove duplicate doc files 2026-03-30 18:04:41 +08:00
xboard
7ed5fc8fd3 fix: remove 2026_03_28_050000_lowercase_existing_emails.php 2026-03-30 17:59:39 +08:00
xboard
5f1afe4bdc feat: add Vless Encryption support 2026-03-30 17:03:37 +08:00
Xboard
0cd20d12dd Merge pull request #755 from socksprox/feat/server-id-stat-user
feat: Track user traffic per node (server_id)
2026-03-30 13:55:11 +08:00
Xboard
b4a94d1605 Merge pull request #689 from socksprox/fix-user-generation-multiple-prefix
Fix user generation with email_prefix to support multiple users
2026-03-30 13:32:46 +08:00
Xboard
7879a9ef85 Merge pull request #786 from lithromantic/master
Add sha256salt hashing option in password verification
2026-03-30 13:05:39 +08:00
xboard
d6a3614d98 update 2026_03_28_161536_add_traffic_fields_to_servers.php 2026-03-30 02:58:09 +08:00
xboard
a58d66d72e feat: node traffic limit & batch operations
- Traffic monitoring with transfer_enable limit
- Batch delete nodes
- Reset traffic (single/batch)
2026-03-30 02:50:56 +08:00
xboard
daf3055b42 fix: escape Telegram Markdown special characters 2026-03-30 01:46:56 +08:00
lithromantic
6cac241144 Merge branch 'cedar2025:master' into master 2026-03-29 00:00:34 +01:00
lithromantic
f6abc362fd Add sha256salt hashing option in password verification 2026-01-18 00:04:00 +01:00
socksprox
c327fecb49 do not return strings, but int 2025-11-29 17:05:07 +01:00
socksprox
0446f88e9e again: update api combining times 2025-11-29 17:05:07 +01:00
socksprox
a01151130e Revert "Combine data with node_id in api output, so its all still "one day", and fits vanilla xboard behaviour"
This reverts commit de39230cbe111bbf793f11bcf5046ef717c67f87.

The api change caused issues
2025-11-29 17:05:07 +01:00
socksprox
9ca8da045c Combine data with node_id in api output, so its all still "one day", and fits vanilla xboard behaviour 2025-11-29 14:07:10 +01:00
socksprox
1ebf86b510 fix: do not merge traffic from different nodes 2025-11-29 13:47:21 +01:00
socksprox
9e35d16fa6 User traffic can now be viewed by node 2025-11-29 13:47:15 +01:00
socksprox
051813d39d Make that user batch generation works again 2025-09-15 15:43:43 +02:00
19 changed files with 390 additions and 55 deletions

View File

@@ -73,6 +73,12 @@ docker compose up -d
This project is for learning and communication purposes only. Users are responsible for any consequences of using this project.
## ❤️ Support The Project
If this project has helped you, donations are appreciated. They help support ongoing maintenance and would make me very happy.
TRC20: `TLypStEWsVrj6Wz9mCxbXffqgt5yz3Y4XB`
## 🌟 Maintenance Notice
This project is currently under light maintenance. We will:

View File

@@ -111,6 +111,94 @@ class ManageController extends Controller
return $this->success(true);
}
/**
* 批量删除节点
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function batchDelete(Request $request)
{
$request->validate([
'ids' => 'required|array',
'ids.*' => 'integer',
]);
$ids = $request->input('ids');
if (empty($ids)) {
return $this->fail([400, '请选择要删除的节点']);
}
try {
$deleted = Server::whereIn('id', $ids)->delete();
if ($deleted === false) {
return $this->fail([500, '批量删除失败']);
}
return $this->success(true);
} catch (\Exception $e) {
Log::error($e);
return $this->fail([500, '批量删除失败']);
}
}
/**
* 重置节点流量
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function resetTraffic(Request $request)
{
$request->validate([
'id' => 'required|integer',
]);
$server = Server::find($request->id);
if (!$server) {
return $this->fail([400202, '服务器不存在']);
}
try {
$server->u = 0;
$server->d = 0;
$server->save();
Log::info("Server {$server->id} ({$server->name}) traffic reset by admin");
return $this->success(true);
} catch (\Exception $e) {
Log::error($e);
return $this->fail([500, '重置失败']);
}
}
/**
* 批量重置节点流量
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function batchResetTraffic(Request $request)
{
$request->validate([
'ids' => 'required|array',
'ids.*' => 'integer',
]);
$ids = $request->input('ids');
if (empty($ids)) {
return $this->fail([400, '请选择要重置的节点']);
}
try {
Server::whereIn('id', $ids)->update([
'u' => 0,
'd' => 0,
]);
Log::info("Servers " . implode(',', $ids) . " traffic reset by admin");
return $this->success(true);
} catch (\Exception $e) {
Log::error($e);
return $this->fail([500, '批量重置失败']);
}
}
/**
* 复制节点
@@ -120,12 +208,17 @@ class ManageController extends Controller
public function copy(Request $request)
{
$server = Server::find($request->input('id'));
$server->show = 0;
$server->code = null;
if (!$server) {
return $this->fail([400202, '服务器不存在']);
}
Server::create($server->toArray());
$copiedServer = $server->replicate();
$copiedServer->show = 0;
$copiedServer->code = null;
$copiedServer->u = 0;
$copiedServer->d = 0;
$copiedServer->save();
return $this->success(true);
}
}

View File

@@ -363,6 +363,12 @@ class UserController extends Controller
public function generate(UserGenerate $request)
{
if ($request->input('email_prefix')) {
// If generate_count is specified with email_prefix, generate multiple users with incremented emails
if ($request->input('generate_count')) {
return $this->multiGenerateWithPrefix($request);
}
// Single user generation with email_prefix
$email = $request->input('email_prefix') . '@' . $request->input('email_suffix');
if (User::byEmail($email)->exists()) {
@@ -459,6 +465,87 @@ class UserController extends Controller
]);
}
private function multiGenerateWithPrefix(Request $request)
{
$userService = app(UserService::class);
$usersData = [];
$emailPrefix = $request->input('email_prefix');
$emailSuffix = $request->input('email_suffix');
$generateCount = $request->input('generate_count');
// Check if any of the emails with prefix already exist
for ($i = 1; $i <= $generateCount; $i++) {
$email = $emailPrefix . '_' . $i . '@' . $emailSuffix;
if (User::where('email', $email)->exists()) {
return $this->fail([400201, '邮箱 ' . $email . ' 已存在于系统中']);
}
}
// Generate user data for batch creation
for ($i = 1; $i <= $generateCount; $i++) {
$email = $emailPrefix . '_' . $i . '@' . $emailSuffix;
$usersData[] = [
'email' => $email,
'password' => $request->input('password') ?? $email,
'plan_id' => $request->input('plan_id'),
'expired_at' => $request->input('expired_at'),
];
}
try {
DB::beginTransaction();
$users = [];
foreach ($usersData as $userData) {
$user = $userService->createUser($userData);
$user->save();
$users[] = $user;
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
return $this->fail([500, '生成失败']);
}
// 判断是否导出 CSV
if ($request->input('download_csv')) {
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="users.csv"',
];
$callback = function () use ($users, $request) {
$handle = fopen('php://output', 'w');
fputcsv($handle, ['账号', '密码', '过期时间', 'UUID', '创建时间', '订阅地址']);
foreach ($users as $user) {
$user = $user->refresh();
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
$subscribeUrl = Helper::getSubscribeUrl($user['token']);
fputcsv($handle, [$user['email'], $password, $expireDate, $user['uuid'], $createDate, $subscribeUrl]);
}
fclose($handle);
};
return response()->streamDownload($callback, 'users.csv', $headers);
}
// 默认返回 JSON
$data = collect($users)->map(function ($user) use ($request) {
return [
'email' => $user['email'],
'password' => $request->input('password') ?? $user['email'],
'expired_at' => $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']),
'uuid' => $user['uuid'],
'created_at' => date('Y-m-d H:i:s', $user['created_at']),
'subscribe_url' => Helper::getSubscribeUrl($user['token']),
];
});
return response()->json([
'code' => 0,
'message' => '批量生成成功',
'data' => $data,
]);
}
public function sendMail(UserSendMail $request)
{
ini_set('memory_limit', '-1');

View File

@@ -71,6 +71,10 @@ class ServerSave extends FormRequest
'network' => 'required|string',
'network_settings' => 'nullable|array',
'flow' => 'nullable|string',
'encryption' => 'nullable|array',
'encryption.enabled' => 'nullable|boolean',
'encryption.encryption' => 'nullable|string',
'encryption.decryption' => 'nullable|string',
'tls_settings.server_name' => 'nullable|string',
'tls_settings.allow_insecure' => 'nullable|boolean',
'reality_settings.allow_insecure' => 'nullable|boolean',
@@ -128,6 +132,7 @@ class ServerSave extends FormRequest
'rate_time_ranges.*.end' => 'required_with:rate_time_ranges|string|date_format:H:i',
'rate_time_ranges.*.rate' => 'required_with:rate_time_ranges|numeric|min:0',
'protocol_settings' => 'array',
'transfer_enable' => 'nullable|integer|min:0',
];
}
@@ -200,6 +205,8 @@ class ServerSave extends FormRequest
'protocol_settings.*.string' => ':attribute 必须是字符串',
'protocol_settings.*.integer' => ':attribute 必须是整数',
'protocol_settings.*.in' => ':attribute 的值不合法',
'transfer_enable.integer' => '流量上限必须是整数',
'transfer_enable.min' => '流量上限不能小于0',
];
}
}

View File

@@ -82,6 +82,9 @@ class AdminRoute
$router->post('/drop', [ManageController::class, 'drop']);
$router->post('/copy', [ManageController::class, 'copy']);
$router->post('/sort', [ManageController::class, 'sort']);
$router->post('/batchDelete', [ManageController::class, 'batchDelete']);
$router->post('/resetTraffic', [ManageController::class, 'resetTraffic']);
$router->post('/batchResetTraffic', [ManageController::class, 'batchResetTraffic']);
});
// Order

View File

@@ -3,12 +3,14 @@
namespace App\Jobs;
use App\Models\Server;
use App\Models\StatServer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@@ -59,12 +61,23 @@ class StatServerJob implements ShouldQueue
try {
$this->processServerStat($u, $d, $recordAt);
$this->updateServerTraffic($u, $d);
} catch (\Exception $e) {
Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage());
throw $e;
}
}
protected function updateServerTraffic(int $u, int $d): void
{
DB::table('v2_server')
->where('id', $this->server['id'])
->incrementEach(
['u' => $u, 'd' => $d],
['updated_at' => Carbon::now()]
);
}
protected function processServerStat(int $u, int $d, int $recordAt): void
{
$driver = config('database.default');

View File

@@ -52,6 +52,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
* @property int|null $d 下行流量
* @property int|null $total 总流量
* @property-read array|null $load_status 负载状态包含CPU、内存、交换区、磁盘信息
*
* @property int $transfer_enable 流量上限0或者null表示不限制
* @property int $u 当前上传流量
* @property int $d 当前下载流量
*/
class Server extends Model
{
@@ -124,6 +128,9 @@ class Server extends Model
'updated_at' => 'timestamp',
'rate_time_ranges' => 'array',
'rate_time_enable' => 'boolean',
'transfer_enable' => 'integer',
'u' => 'integer',
'd' => 'integer',
];
private const MULTIPLEX_CONFIGURATION = [
@@ -196,6 +203,15 @@ class Server extends Model
'tls' => ['type' => 'integer', 'default' => 0],
'tls_settings' => ['type' => 'array', 'default' => null],
'flow' => ['type' => 'string', 'default' => null],
'encryption' => [
'type' => 'object',
'default' => null,
'fields' => [
'enabled' => ['type' => 'boolean', 'default' => false],
'encryption' => ['type' => 'string', 'default' => null], // 客户端公钥
'decryption' => ['type' => 'string', 'default' => null], // 服务端私钥
]
],
'network' => ['type' => 'string', 'default' => null],
'network_settings' => ['type' => 'array', 'default' => null],
...self::REALITY_CONFIGURATION,

View File

@@ -332,6 +332,10 @@ class ClashMeta extends AbstractProtocol
'cipher' => 'auto',
'udp' => true,
'flow' => data_get($protocol_settings, 'flow'),
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
default => 'none'
},
'tls' => false
];

View File

@@ -151,7 +151,10 @@ class General extends AbstractProtocol
$config = [
'mode' => 'multi', //grpc传输模式
'security' => '', //传输层安全 tls/reality
'encryption' => 'none', //加密方式
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
default => 'none'
},
'type' => data_get($server, 'protocol_settings.network'), //传输协议
'flow' => data_get($protocol_settings, 'flow'),
];

View File

@@ -15,6 +15,7 @@ class Loon extends AbstractProtocol
Server::TYPE_TROJAN,
Server::TYPE_HYSTERIA,
Server::TYPE_VLESS,
Server::TYPE_ANYTLS,
];
protected $protocolRequirements = [
@@ -47,6 +48,9 @@ class Loon extends AbstractProtocol
if ($item['type'] === Server::TYPE_VLESS) {
$uri .= self::buildVless($item['password'], $item);
}
if ($item['type'] === Server::TYPE_ANYTLS) {
$uri .= self::buildAnyTLS($item['password'], $item);
}
}
return response($uri)
->header('content-type', 'text/plain')
@@ -325,4 +329,29 @@ class Loon extends AbstractProtocol
$uri .= "\r\n";
return $uri;
}
public static function buildAnyTLS($password, $server)
{
$protocol_settings = data_get($server, 'protocol_settings', []);
$config = [
"{$server['name']}=anytls",
"{$server['host']}",
"{$server['port']}",
"{$password}",
"udp=true"
];
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
$config[] = "sni={$serverName}";
}
// ✅ 跳过证书校验
if (data_get($protocol_settings, 'tls.allow_insecure')) {
$config[] = 'skip-cert-verify=true';
}
$config = array_filter($config);
return implode(',', $config) . "\r\n";
}
}

View File

@@ -14,6 +14,7 @@ class Surfboard extends AbstractProtocol
Server::TYPE_SHADOWSOCKS,
Server::TYPE_VMESS,
Server::TYPE_TROJAN,
Server::TYPE_ANYTLS,
];
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surfboard.conf';
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surfboard.conf';
@@ -36,7 +37,10 @@ class Surfboard extends AbstractProtocol
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
'chacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305'
])
) {
// [Proxy]
@@ -56,6 +60,10 @@ class Surfboard extends AbstractProtocol
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === Server::TYPE_ANYTLS) {
$proxies .= self::buildAnyTLS($item['password'], $item);
$proxyGroup .= $item['name'] . ', ';
}
}
$config = subscribe_template('surfboard');
@@ -190,4 +198,32 @@ class Surfboard extends AbstractProtocol
$uri .= "\r\n";
return $uri;
}
public static function buildAnyTLS($password, $server)
{
$protocol_settings = data_get($server, 'protocol_settings', []);
$config = [
"{$server['name']}=anytls",
"{$server['host']}",
"{$server['port']}",
"password={$password}",
"tfo=true",
"udp-relay=true"
];
// SNI
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
$config[] = "sni={$serverName}";
}
// 跳过证书校验
if (data_get($protocol_settings, 'tls.allow_insecure')) {
$config[] = "skip-cert-verify=true";
}
$config = array_filter($config);
return implode(',', $config) . "\r\n";
}
}

View File

@@ -46,7 +46,7 @@ class MailLinkService
$this->sendMailLinkEmail($user, $link);
return [true, $link];
return [true, true];
}
/**

View File

@@ -42,6 +42,11 @@ class ServerService
{
$servers = Server::whereJsonContains('group_ids', (string) $user->group_id)
->where('show', true)
->where(function ($query) {
$query->whereNull('transfer_enable')
->orWhere('transfer_enable', 0)
->orWhereRaw('u + d < transfer_enable');
})
->orderBy('sort', 'ASC')
->get()
->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']);
@@ -178,6 +183,10 @@ class ServerService
...$baseConfig,
'tls' => (int) $protocolSettings['tls'],
'flow' => $protocolSettings['flow'],
'decryption' => match (data_get($protocolSettings, 'encryption.enabled')) {
true => data_get($protocolSettings, 'encryption.decryption'),
default => null,
},
'tls_settings' => match ((int) $protocolSettings['tls']) {
2 => $protocolSettings['reality_settings'],
default => $protocolSettings['tls_settings'],
@@ -244,10 +253,10 @@ class ServerService
default => [],
};
$response = array_filter(
$response,
static fn ($value) => $value !== null
);
// $response = array_filter(
// $response,
// static fn ($value) => $value !== null
// );
if (!empty($node['route_ids'])) {
$response['routes'] = self::getRoutes($node['route_ids']);

View File

@@ -86,6 +86,7 @@ class Helper
case 'md5': return md5($password) === $hash;
case 'sha256': return hash('sha256', $password) === $hash;
case 'md5salt': return md5($password . $salt) === $hash;
case 'sha256salt': return hash('sha256', $password . $salt) === $hash;
default: return password_verify($password, $hash);
}
}
@@ -229,4 +230,14 @@ class Helper
{
return $transfer_enable / 1073741824;
}
/**
* 转义 Telegram Markdown 特殊字符
* @param string $text
* @return string
*/
public static function escapeMarkdown(string $text): string
{
return str_replace(['_', '*', '`', '['], ['\_', '\*', '\`', '\['], $text);
}
}

View File

@@ -1,38 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// 统计需要转换的记录数
$count = DB::table('v2_user')
->whereNotNull('email')
->whereRaw('email != LOWER(email)')
->count();
if ($count > 0) {
Log::info("Converting {$count} email(s) to lowercase");
DB::table('v2_user')
->whereNotNull('email')
->whereRaw('email != LOWER(email)')
->update(['email' => DB::raw('LOWER(email)')]);
Log::info("Email lowercase conversion completed");
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// 无法恢复原始大小写
}
};

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v2_server', function (Blueprint $table) {
if (!Schema::hasColumn('v2_server', 'transfer_enable')) {
$table->bigInteger('transfer_enable')
->default(null)
->nullable()
->after('rate')
->comment('Traffic limit , 0 or null=no limit');
}
if (!Schema::hasColumn('v2_server', 'u')) {
$table->bigInteger('u')
->default(0)
->after('transfer_enable')
->comment('upload traffic');
}
if (!Schema::hasColumn('v2_server', 'd')) {
$table->bigInteger('d')
->default(0)
->after('u')
->comment('donwload traffic');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v2_server', function (Blueprint $table) {
$table->dropColumn(['transfer_enable', 'u', 'd']);
});
}
};

View File

@@ -58,8 +58,8 @@ class Plugin extends AbstractPlugin
"支付渠道:%s\n" .
"本站订单:`%s`",
$order->total_amount / 100,
$payment->payment,
$payment->name,
Helper::escapeMarkdown($payment->payment),
Helper::escapeMarkdown($payment->name),
$order->trade_no
);
$this->telegramService->sendMessageWithAdmin($message, true);
@@ -92,7 +92,7 @@ class Plugin extends AbstractPlugin
$TGmessage .= "📍 位置: `{$region}`\n";
if ($plan) {
$TGmessage .= "📦 套餐: `{$plan->name}`\n";
$TGmessage .= "📦 套餐: `" . Helper::escapeMarkdown($plan->name) . "`\n";
$TGmessage .= "📊 流量: `{$remaining_traffic}G / {$transfer_enable}G` (剩余/总计)\n";
$TGmessage .= "⬆️⬇️ 已用: `{$u}G / {$d}G`\n";
$TGmessage .= "⏰ 到期: `{$expired_at}`\n";
@@ -103,8 +103,8 @@ class Plugin extends AbstractPlugin
$TGmessage .= "💰 余额: `{$money}元`\n";
$TGmessage .= "💸 佣金: `{$affmoney}元`\n";
$TGmessage .= "━━━━━━━━━━━━━━━━━━━━\n";
$TGmessage .= "📝 *主题*: `{$ticket->subject}`\n";
$TGmessage .= "💬 *内容*: `{$message->message}`";
$TGmessage .= "📝 *主题*: `" . Helper::escapeMarkdown($ticket->subject) . "`\n";
$TGmessage .= "💬 *内容*: `" . Helper::escapeMarkdown($message->message) . "`";
$this->telegramService->sendMessageWithAdmin($TGmessage, true);
}

View File

@@ -10,7 +10,17 @@ if ! command -v git &> /dev/null; then
exit 1
fi
git config --global --add safe.directory $(pwd)
repo_root="$(pwd)"
add_safe_directory() {
local dir="$1"
git config --global --get-all safe.directory | grep -Fx "$dir" > /dev/null || git config --global --add safe.directory "$dir"
}
add_safe_directory "$repo_root"
add_safe_directory "$repo_root/public/assets/admin"
git fetch --all && git reset --hard origin/master && git pull origin master
rm -rf composer.lock composer.phar
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar