mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-01-22 18:48:14 -05:00
Compare commits
3 Commits
70998d0c08
...
9cd3859def
Author | SHA1 | Date | |
---|---|---|---|
|
9cd3859def | ||
|
cf10a45d2d | ||
|
8e7581ed8e |
64
.docker/supervisor/supervisord.conf
Normal file
64
.docker/supervisor/supervisord.conf
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/dev/stdout
|
||||||
|
logfile_maxbytes=0
|
||||||
|
pidfil=/www/storage/logs/supervisor/supervisord.pid
|
||||||
|
loglevel=info
|
||||||
|
|
||||||
|
[program:octane]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /www/artisan octane:start --host=0.0.0.0 --port=7001
|
||||||
|
autostart=%(ENV_ENABLE_WEB)s
|
||||||
|
autorestart=true
|
||||||
|
user=www
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=10
|
||||||
|
stopsignal=QUIT
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=100
|
||||||
|
|
||||||
|
[program:horizon]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /www/artisan horizon
|
||||||
|
autostart=%(ENV_ENABLE_HORIZON)s
|
||||||
|
autorestart=true
|
||||||
|
user=www
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=3
|
||||||
|
stopsignal=SIGINT
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=200
|
||||||
|
|
||||||
|
[program:redis]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=redis-server --dir /data
|
||||||
|
--dbfilename dump.rdb
|
||||||
|
--save 900 1
|
||||||
|
--save 300 10
|
||||||
|
--save 60 10000
|
||||||
|
--unixsocket /data/redis.sock
|
||||||
|
--unixsocketperm 777
|
||||||
|
autostart=%(ENV_ENABLE_REDIS)s
|
||||||
|
autorestart=true
|
||||||
|
user=redis
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=3
|
||||||
|
stopsignal=TERM
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=300
|
26
Dockerfile
26
Dockerfile
@ -1,23 +1,25 @@
|
|||||||
FROM phpswoole/swoole:php8.1-alpine
|
FROM phpswoole/swoole:php8.1-alpine
|
||||||
|
|
||||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
RUN install-php-extensions pcntl bcmath \
|
RUN install-php-extensions pcntl bcmath \
|
||||||
&& apk --no-cache add shadow sqlite mysql-client git patch \
|
&& apk --no-cache add shadow sqlite mysql-client git patch supervisor redis \
|
||||||
&& addgroup -S -g 1000 www && adduser -S -G www -u 1000 www
|
&& addgroup -S -g 1000 www && adduser -S -G www -u 1000 www \
|
||||||
#复制项目文件以及配置文件
|
&& (getent group redis || addgroup -S redis) \
|
||||||
|
&& (getent passwd redis || adduser -S -G redis -H -h /data redis)
|
||||||
|
|
||||||
WORKDIR /www
|
WORKDIR /www
|
||||||
COPY .docker /
|
COPY .docker /
|
||||||
COPY . /www
|
COPY . /www
|
||||||
|
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
RUN composer install --optimize-autoloader --no-cache --no-dev \
|
RUN composer install --optimize-autoloader --no-cache --no-dev \
|
||||||
&& php artisan storage:link \
|
&& php artisan storage:link \
|
||||||
&& chown -R www:www /www \
|
&& chown -R www:www /www \
|
||||||
&& chmod -R 775 /www
|
&& chmod -R 775 /www \
|
||||||
|
&& mkdir -p /data \
|
||||||
|
&& chown redis:redis /data
|
||||||
|
|
||||||
CMD php artisan octane:start \
|
ENV ENABLE_WEB=false \
|
||||||
--server="swoole" \
|
ENABLE_HORIZON=false \
|
||||||
--host=0.0.0.0 \
|
ENABLE_REDIS=false
|
||||||
--port=${OCTANE_PORT:-7001} \
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
--workers=${OCTANE_WORKERS:-auto} \
|
|
||||||
--task-workers=${OCTANE_TASK_WORKERS:-auto} \
|
|
||||||
--max-requests=${OCTANE_MAX_REQUESTS:-500}
|
|
@ -31,14 +31,13 @@ Xboard New是基于Xboard二次开发,重写后台管理并优化系统架构
|
|||||||
git clone -b compose-new --depth 1 https://github.com/cedar2025/Xboard && \
|
git clone -b compose-new --depth 1 https://github.com/cedar2025/Xboard && \
|
||||||
cd Xboard && \
|
cd Xboard && \
|
||||||
docker compose run -it --rm \
|
docker compose run -it --rm \
|
||||||
-e enable_sqlite=true \
|
-e ENABLE_SQLITE=true \
|
||||||
-e enable_redis=true \
|
-e ENABLE_REDIS=true \
|
||||||
-e admin_account=admin@demo.com \
|
-e ADMIN_ACCOUNT=admin@demo.com \
|
||||||
web php artisan xboard:install && \
|
web php artisan xboard:install && \
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 安装完成后访问 http://服务器IP:7001
|
|
||||||
```
|
```
|
||||||
|
安装完成后访问 http://服务器IP:7001
|
||||||
|
|
||||||
> 提示:安装过程中会显示管理员账号密码,请务必保存。
|
> 提示:安装过程中会显示管理员账号密码,请务必保存。
|
||||||
|
|
||||||
|
@ -45,10 +45,10 @@ class XboardInstall extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$isDocker = env('docker', false);
|
$isDocker = file_exists('/.dockerenv');
|
||||||
$enableSqlite = env('enable_sqlite', false);
|
$enableSqlite = env('ENABLE_SQLITE', false);
|
||||||
$enableRedis = env('enable_redis', false);
|
$enableRedis = env('ENABLE_REDIS', false);
|
||||||
$adminAccount = env('admin_account', '');
|
$adminAccount = env('ADMIN_ACCOUNT', '');
|
||||||
$this->info("__ __ ____ _ ");
|
$this->info("__ __ ____ _ ");
|
||||||
$this->info("\ \ / /| __ ) ___ __ _ _ __ __| | ");
|
$this->info("\ \ / /| __ ) ___ __ _ _ __ __| | ");
|
||||||
$this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | ");
|
$this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | ");
|
||||||
@ -146,7 +146,7 @@ class XboardInstall extends Command
|
|||||||
while (!$isReidsValid) {
|
while (!$isReidsValid) {
|
||||||
// 判断是否为Docker环境
|
// 判断是否为Docker环境
|
||||||
if ($isDocker == 'true' && ($enableRedis || confirm(label: '是否启用Docker内置的Redis', default: true, yes: '启用', no: '不启用'))) {
|
if ($isDocker == 'true' && ($enableRedis || confirm(label: '是否启用Docker内置的Redis', default: true, yes: '启用', no: '不启用'))) {
|
||||||
$envConfig['REDIS_HOST'] = '/run/redis-socket/redis.sock';
|
$envConfig['REDIS_HOST'] = '/data/redis.sock';
|
||||||
$envConfig['REDIS_PORT'] = 0;
|
$envConfig['REDIS_PORT'] = 0;
|
||||||
$envConfig['REDIS_PASSWORD'] = null;
|
$envConfig['REDIS_PASSWORD'] = null;
|
||||||
} else {
|
} else {
|
||||||
@ -171,6 +171,7 @@ class XboardInstall extends Command
|
|||||||
// 连接失败,输出错误消息
|
// 连接失败,输出错误消息
|
||||||
$this->error("redis连接失败:" . $e->getMessage());
|
$this->error("redis连接失败:" . $e->getMessage());
|
||||||
$this->info("请重新输入REDIS配置");
|
$this->info("请重新输入REDIS配置");
|
||||||
|
sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
namespace App\Http\Controllers\V1\Guest;
|
namespace App\Http\Controllers\V1\Guest;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\PlanResources;
|
use App\Http\Resources\PlanResource;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
use Auth;
|
use Auth;
|
||||||
@ -20,6 +20,6 @@ class PlanController extends Controller
|
|||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$plan = $this->planService->getAvailablePlans();
|
$plan = $this->planService->getAvailablePlans();
|
||||||
return $this->success(PlanResources::collection($plan));
|
return $this->success(PlanResource::collection($plan));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers\V1\User;
|
|||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\User\OrderSave;
|
use App\Http\Requests\User\OrderSave;
|
||||||
use App\Http\Resources\OrderResources;
|
use App\Http\Resources\OrderResource;
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
@ -34,7 +34,7 @@ class OrderController extends Controller
|
|||||||
->orderBy('created_at', 'DESC')
|
->orderBy('created_at', 'DESC')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return $this->success(OrderResources::collection($orders));
|
return $this->success(OrderResource::collection($orders));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function detail(Request $request)
|
public function detail(Request $request)
|
||||||
@ -57,7 +57,7 @@ class OrderController extends Controller
|
|||||||
if ($order->surplus_order_ids) {
|
if ($order->surplus_order_ids) {
|
||||||
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
||||||
}
|
}
|
||||||
return $this->success(OrderResources::make($order));
|
return $this->success(OrderResource::make($order));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(OrderSave $request)
|
public function save(OrderSave $request)
|
||||||
|
@ -4,7 +4,7 @@ namespace App\Http\Controllers\V1\User;
|
|||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\PlanResources;
|
use App\Http\Resources\PlanResource;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
@ -29,10 +29,10 @@ class PlanController extends Controller
|
|||||||
if (!$this->planService->isPlanAvailableForUser($plan, $user)) {
|
if (!$this->planService->isPlanAvailableForUser($plan, $user)) {
|
||||||
return $this->fail([400, __('Subscription plan does not exist')]);
|
return $this->fail([400, __('Subscription plan does not exist')]);
|
||||||
}
|
}
|
||||||
return $this->success(PlanResources::make($plan));
|
return $this->success(PlanResource::make($plan));
|
||||||
}
|
}
|
||||||
|
|
||||||
$plans = $this->planService->getAvailablePlans();
|
$plans = $this->planService->getAvailablePlans();
|
||||||
return $this->success(PlanResources::collection($plans));
|
return $this->success(PlanResource::collection($plans));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,8 @@ class ConfigSave extends FormRequest
|
|||||||
'recaptcha_enable' => 'boolean',
|
'recaptcha_enable' => 'boolean',
|
||||||
'recaptcha_key' => '',
|
'recaptcha_key' => '',
|
||||||
'recaptcha_site_key' => '',
|
'recaptcha_site_key' => '',
|
||||||
// 'email_verify' => 'bool|',
|
'email_verify' => 'bool',
|
||||||
// 'safe_mode_enable' => 'boolean',
|
'safe_mode_enable' => 'boolean',
|
||||||
'register_limit_by_ip_enable' => 'boolean',
|
'register_limit_by_ip_enable' => 'boolean',
|
||||||
'register_limit_count' => 'integer',
|
'register_limit_count' => 'integer',
|
||||||
'register_limit_expire' => 'integer',
|
'register_limit_expire' => 'integer',
|
||||||
|
@ -6,7 +6,7 @@ use App\Services\PlanService;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class OrderResources extends JsonResource
|
class OrderResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
@ -18,7 +18,7 @@ class OrderResources extends JsonResource
|
|||||||
return [
|
return [
|
||||||
...parent::toArray($request),
|
...parent::toArray($request),
|
||||||
'period' => PlanService::getLegacyPeriod($this->period),
|
'period' => PlanService::getLegacyPeriod($this->period),
|
||||||
'plan' => PlanResources::make($this->plan),
|
'plan' => PlanResource::make($this->plan),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
76
app/Http/Resources/PlanResource.php
Normal file
76
app/Http/Resources/PlanResource.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Services\PlanService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class PlanResource extends JsonResource
|
||||||
|
{
|
||||||
|
private const PRICE_MULTIPLIER = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->resource['id'],
|
||||||
|
'group_id' => $this->resource['group_id'],
|
||||||
|
'name' => $this->resource['name'],
|
||||||
|
'content' => $this->resource['content'],
|
||||||
|
...$this->getPeriodPrices(),
|
||||||
|
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
||||||
|
'transfer_enable' => $this->resource['transfer_enable'],
|
||||||
|
'speed_limit' => $this->resource['speed_limit'],
|
||||||
|
'show' => (bool) $this->resource['show'],
|
||||||
|
'sell' => (bool) $this->resource['sell'],
|
||||||
|
'renew' => (bool) $this->resource['renew'],
|
||||||
|
'reset_traffic_method' => $this->resource['reset_traffic_method'],
|
||||||
|
'sort' => $this->resource['sort'],
|
||||||
|
'created_at' => $this->resource['created_at'],
|
||||||
|
'updated_at' => $this->resource['updated_at']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transformed period prices using Plan mapping
|
||||||
|
*
|
||||||
|
* @return array<string, float|null>
|
||||||
|
*/
|
||||||
|
protected function getPeriodPrices(): array
|
||||||
|
{
|
||||||
|
return collect(Plan::LEGACY_PERIOD_MAPPING)
|
||||||
|
->mapWithKeys(function (string $newPeriod, string $legacyPeriod): array {
|
||||||
|
$price = $this->resource['prices'][$newPeriod] ?? null;
|
||||||
|
return [
|
||||||
|
$legacyPeriod => $price !== null
|
||||||
|
? (float) $price * self::PRICE_MULTIPLIER
|
||||||
|
: null
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formatted capacity limit value
|
||||||
|
*
|
||||||
|
* @return int|string|null
|
||||||
|
*/
|
||||||
|
protected function getFormattedCapacityLimit(): int|string|null
|
||||||
|
{
|
||||||
|
$limit = $this->resource['capacity_limit'];
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
$limit === null => null,
|
||||||
|
$limit <= 0 => __('Sold out'),
|
||||||
|
default => (int) $limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,71 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
|
||||||
|
|
||||||
use App\Models\Plan;
|
|
||||||
use App\Services\PlanService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
|
|
||||||
class PlanResources extends JsonResource
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function toArray(Request $request): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $this['id'],
|
|
||||||
'group_id' => $this['group_id'],
|
|
||||||
'name' => $this['name'],
|
|
||||||
'content' => $this['content'],
|
|
||||||
...$this->transformPeriodPrices(),
|
|
||||||
'capacity_limit' => $this->formatCapacityLimit(),
|
|
||||||
'transfer_enable' => $this['transfer_enable'],
|
|
||||||
'speed_limit' => $this['speed_limit'],
|
|
||||||
'show' => (bool) $this['show'],
|
|
||||||
'sell' => (bool) $this['sell'],
|
|
||||||
'renew' => (bool) $this['renew'],
|
|
||||||
'reset_traffic_method' => $this['reset_traffic_method'],
|
|
||||||
'sort' => $this['sort'],
|
|
||||||
'created_at' => $this['created_at'],
|
|
||||||
'updated_at' => $this['updated_at']
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform period prices using PlanService mapping
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
protected function transformPeriodPrices(): array
|
|
||||||
{
|
|
||||||
$prices = [];
|
|
||||||
foreach (Plan::LEGACY_PERIOD_MAPPING as $legacyPeriod => $newPeriod) {
|
|
||||||
$prices[$legacyPeriod] = optional($this['prices'])[$newPeriod] ? (float) $this['prices'][$newPeriod] * 100 : null;
|
|
||||||
}
|
|
||||||
return $prices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the capacity limit value
|
|
||||||
*
|
|
||||||
* @return int|string|null
|
|
||||||
*/
|
|
||||||
protected function formatCapacityLimit(): int|string|null
|
|
||||||
{
|
|
||||||
if ($this['capacity_limit'] === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this['capacity_limit'] <= 0) {
|
|
||||||
return __('Sold out');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) $this['capacity_limit'];
|
|
||||||
}
|
|
||||||
}
|
|
26
bin/fswatch
26
bin/fswatch
@ -1,26 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
WORK_DIR=$1
|
|
||||||
if [ ! -n "${WORK_DIR}" ] ;then
|
|
||||||
WORK_DIR="."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Restarting LaravelS..."
|
|
||||||
./bin/laravels restart -d -i
|
|
||||||
|
|
||||||
echo "Starting fswatch..."
|
|
||||||
LOCKING=0
|
|
||||||
fswatch -e ".*" -i "\\.php$" -r ${WORK_DIR} | while read file
|
|
||||||
do
|
|
||||||
if [[ ! ${file} =~ .php$ ]] ;then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ ${LOCKING} -eq 1 ] ;then
|
|
||||||
echo "Reloading, skipped."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "File ${file} has been modified."
|
|
||||||
LOCKING=1
|
|
||||||
./bin/laravels reload
|
|
||||||
LOCKING=0
|
|
||||||
done
|
|
||||||
exit 0
|
|
28
bin/inotify
28
bin/inotify
@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
WORK_DIR=$1
|
|
||||||
if [ ! -n "${WORK_DIR}" ] ;then
|
|
||||||
WORK_DIR="."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Restarting LaravelS..."
|
|
||||||
./bin/laravels restart -d -i
|
|
||||||
|
|
||||||
echo "Starting inotifywait..."
|
|
||||||
LOCKING=0
|
|
||||||
|
|
||||||
inotifywait --event modify --event create --event move --event delete -mrq ${WORK_DIR} | while read file
|
|
||||||
|
|
||||||
do
|
|
||||||
if [[ ! ${file} =~ .php$ ]] ;then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ ${LOCKING} -eq 1 ] ;then
|
|
||||||
echo "Reloading, skipped."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "File ${file} has been modified."
|
|
||||||
LOCKING=1
|
|
||||||
./bin/laravels reload
|
|
||||||
LOCKING=0
|
|
||||||
done
|
|
||||||
exit 0
|
|
168
bin/laravels
168
bin/laravels
@ -1,168 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This autoloader is only used to pull laravel-s.
|
|
||||||
* Class Psr4Autoloader
|
|
||||||
*/
|
|
||||||
class Psr4Autoloader
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An associative array where the key is a namespace prefix and the value
|
|
||||||
* is an array of base directories for classes in that namespace.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $prefixes = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register loader with SPL autoloader stack.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function register()
|
|
||||||
{
|
|
||||||
spl_autoload_register([$this, 'loadClass']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a base directory for a namespace prefix.
|
|
||||||
*
|
|
||||||
* @param string $prefix The namespace prefix.
|
|
||||||
* @param string $base_dir A base directory for class files in the
|
|
||||||
* namespace.
|
|
||||||
* @param bool $prepend If true, prepend the base directory to the stack
|
|
||||||
* instead of appending it; this causes it to be searched first rather
|
|
||||||
* than last.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function addNamespace($prefix, $base_dir, $prepend = false)
|
|
||||||
{
|
|
||||||
// normalize namespace prefix
|
|
||||||
$prefix = trim($prefix, '\\') . '\\';
|
|
||||||
|
|
||||||
// normalize the base directory with a trailing separator
|
|
||||||
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
|
|
||||||
|
|
||||||
// initialize the namespace prefix array
|
|
||||||
if (isset($this->prefixes[$prefix]) === false) {
|
|
||||||
$this->prefixes[$prefix] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// retain the base directory for the namespace prefix
|
|
||||||
if ($prepend) {
|
|
||||||
array_unshift($this->prefixes[$prefix], $base_dir);
|
|
||||||
} else {
|
|
||||||
$this->prefixes[$prefix][] = $base_dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the class file for a given class name.
|
|
||||||
*
|
|
||||||
* @param string $class The fully-qualified class name.
|
|
||||||
* @return mixed The mapped file name on success, or boolean false on
|
|
||||||
* failure.
|
|
||||||
*/
|
|
||||||
public function loadClass($class)
|
|
||||||
{
|
|
||||||
// the current namespace prefix
|
|
||||||
$prefix = $class;
|
|
||||||
|
|
||||||
// work backwards through the namespace names of the fully-qualified
|
|
||||||
// class name to find a mapped file name
|
|
||||||
while (false !== $pos = strrpos($prefix, '\\')) {
|
|
||||||
// retain the trailing namespace separator in the prefix
|
|
||||||
$prefix = substr($class, 0, $pos + 1);
|
|
||||||
|
|
||||||
// the rest is the relative class name
|
|
||||||
$relative_class = substr($class, $pos + 1);
|
|
||||||
|
|
||||||
// try to load a mapped file for the prefix and relative class
|
|
||||||
$mapped_file = $this->loadMappedFile($prefix, $relative_class);
|
|
||||||
if ($mapped_file) {
|
|
||||||
return $mapped_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the trailing namespace separator for the next iteration
|
|
||||||
// of strrpos()
|
|
||||||
$prefix = rtrim($prefix, '\\');
|
|
||||||
}
|
|
||||||
|
|
||||||
// never found a mapped file
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the mapped file for a namespace prefix and relative class.
|
|
||||||
*
|
|
||||||
* @param string $prefix The namespace prefix.
|
|
||||||
* @param string $relative_class The relative class name.
|
|
||||||
* @return mixed Boolean false if no mapped file can be loaded, or the
|
|
||||||
* name of the mapped file that was loaded.
|
|
||||||
*/
|
|
||||||
protected function loadMappedFile($prefix, $relative_class)
|
|
||||||
{
|
|
||||||
// are there any base directories for this namespace prefix?
|
|
||||||
if (isset($this->prefixes[$prefix]) === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// look through base directories for this namespace prefix
|
|
||||||
foreach ($this->prefixes[$prefix] as $base_dir) {
|
|
||||||
// replace the namespace prefix with the base directory,
|
|
||||||
// replace namespace separators with directory separators
|
|
||||||
// in the relative class name, append with .php
|
|
||||||
$file = $base_dir
|
|
||||||
. str_replace('\\', '/', $relative_class)
|
|
||||||
. '.php';
|
|
||||||
|
|
||||||
// if the mapped file exists, require it
|
|
||||||
if ($this->requireFile($file)) {
|
|
||||||
// yes, we're done
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// never found it
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a file exists, require it from the file system.
|
|
||||||
*
|
|
||||||
* @param string $file The file to require.
|
|
||||||
* @return bool True if the file exists, false if not.
|
|
||||||
*/
|
|
||||||
public function requireFile($file)
|
|
||||||
{
|
|
||||||
if (file_exists($file)) {
|
|
||||||
require $file;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$basePath = dirname(__DIR__) . '/';
|
|
||||||
$loader = new Psr4Autoloader();
|
|
||||||
$loader->register();
|
|
||||||
|
|
||||||
// Register laravel-s
|
|
||||||
$loader->addNamespace('Hhxsv5\LaravelS', $basePath . '/vendor/hhxsv5/laravel-s/src');
|
|
||||||
|
|
||||||
// Register laravel-s dependencies
|
|
||||||
|
|
||||||
// To fix issue #364 https://github.com/hhxsv5/laravel-s/issues/364
|
|
||||||
$loader->addNamespace('Symfony\Polyfill\Php80', $basePath . '/vendor/symfony/polyfill-php80');
|
|
||||||
$loader->requireFile($basePath . '/vendor/symfony/polyfill-php80/bootstrap.php');
|
|
||||||
|
|
||||||
$loader->addNamespace('Symfony\Component\Console', $basePath . '/vendor/symfony/console');
|
|
||||||
$loader->addNamespace('Symfony\Contracts\Service', $basePath . '/vendor/symfony/service-contracts');
|
|
||||||
$loader->addNamespace('Symfony\Contracts', $basePath . '/vendor/symfony/contracts');
|
|
||||||
|
|
||||||
$command = new Hhxsv5\LaravelS\Console\Portal($basePath);
|
|
||||||
$input = new Symfony\Component\Console\Input\ArgvInput();
|
|
||||||
$output = new Symfony\Component\Console\Output\ConsoleOutput();
|
|
||||||
$code = $command->run($input, $output);
|
|
||||||
exit($code);
|
|
@ -2,19 +2,19 @@ services:
|
|||||||
web:
|
web:
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
image: ghcr.io/cedar2025/xboard:new
|
||||||
volumes:
|
volumes:
|
||||||
- ./.docker/.data/redis/:/run/redis-socket
|
- ./.docker/.data/redis/:/data/
|
||||||
- ./:/www/
|
- ./:/www/
|
||||||
environment:
|
environment:
|
||||||
- docker=true
|
- docker=true
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
network_mode: host
|
network_mode: host
|
||||||
command: php artisan octane:start --server="swoole" --port=7001 --host=0.0.0.0
|
command: php artisan octane:start --port=7001 --host=0.0.0.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
horizon:
|
horizon:
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
image: ghcr.io/cedar2025/xboard:new
|
||||||
volumes:
|
volumes:
|
||||||
- ./.docker/.data/redis/:/run/redis-socket
|
- ./.docker/.data/redis/:/data/
|
||||||
- ./:/www/
|
- ./:/www/
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
@ -36,7 +36,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'server' => env('OCTANE_SERVER', 'roadrunner'),
|
'server' => env('OCTANE_SERVER', 'swoole'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| HTTP server configurations.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| @see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'server' => [
|
|
||||||
'host' => env('SWOOLE_HTTP_HOST', '0.0.0.0'),
|
|
||||||
'port' => env('SWOOLE_HTTP_PORT', '1215'),
|
|
||||||
'public_path' => base_path('public'),
|
|
||||||
// Determine if to use swoole to respond request for static files
|
|
||||||
'handle_static_files' => env('SWOOLE_HANDLE_STATIC', true),
|
|
||||||
'access_log' => env('SWOOLE_HTTP_ACCESS_LOG', false),
|
|
||||||
// You must add --enable-openssl while compiling Swoole
|
|
||||||
// Put `SWOOLE_SOCK_TCP | SWOOLE_SSL` if you want to enable SSL
|
|
||||||
'socket_type' => SWOOLE_SOCK_TCP,
|
|
||||||
'process_type' => SWOOLE_PROCESS,
|
|
||||||
'options' => [
|
|
||||||
'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
|
|
||||||
'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
|
|
||||||
'daemonize' => env('SWOOLE_HTTP_DAEMONIZE', false),
|
|
||||||
// Normally this value should be 1~4 times larger according to your cpu cores.
|
|
||||||
'reactor_num' => env('SWOOLE_HTTP_REACTOR_NUM', swoole_cpu_num()),
|
|
||||||
'worker_num' => env('SWOOLE_HTTP_WORKER_NUM', swoole_cpu_num()),
|
|
||||||
'task_worker_num' => env('SWOOLE_HTTP_TASK_WORKER_NUM', swoole_cpu_num()),
|
|
||||||
// The data to receive can't be larger than buffer_output_size.
|
|
||||||
'package_max_length' => 20 * 1024 * 1024,
|
|
||||||
// The data to send can't be larger than buffer_output_size.
|
|
||||||
'buffer_output_size' => 10 * 1024 * 1024,
|
|
||||||
// Max buffer size for socket connections
|
|
||||||
'socket_buffer_size' => 128 * 1024 * 1024,
|
|
||||||
// Worker will restart after processing this number of requests
|
|
||||||
'max_request' => 3000,
|
|
||||||
// Enable coroutine send
|
|
||||||
'send_yield' => true,
|
|
||||||
// You must add --enable-openssl while compiling Swoole
|
|
||||||
'ssl_cert_file' => null,
|
|
||||||
'ssl_key_file' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Enable to turn on websocket server.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'websocket' => [
|
|
||||||
'enabled' => env('SWOOLE_HTTP_WEBSOCKET', false),
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Hot reload configuration
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'hot_reload' => [
|
|
||||||
'enabled' => env('SWOOLE_HOT_RELOAD_ENABLE', false),
|
|
||||||
'recursively' => env('SWOOLE_HOT_RELOAD_RECURSIVELY', true),
|
|
||||||
'directory' => env('SWOOLE_HOT_RELOAD_DIRECTORY', base_path()),
|
|
||||||
'log' => env('SWOOLE_HOT_RELOAD_LOG', true),
|
|
||||||
'filter' => env('SWOOLE_HOT_RELOAD_FILTER', '.php'),
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Console output will be transferred to response content if enabled.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'ob_output' => env('SWOOLE_OB_OUTPUT', true),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Pre-resolved instances here will be resolved when sandbox created.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'pre_resolved' => [
|
|
||||||
'view', 'files', 'session', 'session.store', 'routes',
|
|
||||||
'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
|
|
||||||
'encrypter', 'hash', 'router', 'translator', 'url', 'log',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Instances here will be cleared on every request.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'instances' => [
|
|
||||||
'auth',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Providers here will be registered on every request.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'providers' => [
|
|
||||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Resetters for sandbox app.
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'resetters' => [
|
|
||||||
SwooleTW\Http\Server\Resetters\ResetConfig::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\ResetSession::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\ResetCookie::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\ClearInstances::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\BindRequest::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\RebindKernelContainer::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\RebindRouterContainer::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\RebindViewContainer::class,
|
|
||||||
SwooleTW\Http\Server\Resetters\ResetProviders::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Define your swoole tables here.
|
|
||||||
|
|
|
||||||
| @see https://www.swoole.co.uk/docs/modules/swoole-table
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'tables' => [
|
|
||||||
// 'table_name' => [
|
|
||||||
// 'size' => 1024,
|
|
||||||
// 'columns' => [
|
|
||||||
// ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
|
|
||||||
// ]
|
|
||||||
// ],
|
|
||||||
],
|
|
||||||
];
|
|
@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Websocket handler for onOpen and onClose callback
|
|
||||||
| Replace this handler if you want to customize your websocket handler
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'handler' => SwooleTW\Http\Websocket\SocketIO\WebsocketHandler::class,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default frame parser
|
|
||||||
| Replace it if you want to customize your websocket payload
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'parser' => SwooleTW\Http\Websocket\SocketIO\SocketIOParser::class,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Websocket route file path
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'route_file' => base_path('routes/websocket.php'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default middleware for on connect request
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'middleware' => [
|
|
||||||
// SwooleTW\Http\Websocket\Middleware\DecryptCookies::class,
|
|
||||||
// SwooleTW\Http\Websocket\Middleware\StartSession::class,
|
|
||||||
// SwooleTW\Http\Websocket\Middleware\Authenticate::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Websocket handler for customized onHandShake callback
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'handshake' => [
|
|
||||||
'enabled' => false,
|
|
||||||
'handler' => SwooleTW\Http\Websocket\HandShakeHandler::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default websocket driver
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'default' => 'table',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Websocket client's heartbeat interval (ms)
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'ping_interval' => 25000,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Websocket client's heartbeat interval timeout (ms)
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'ping_timeout' => 60000,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Room drivers mapping
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'drivers' => [
|
|
||||||
'table' => SwooleTW\Http\Websocket\Rooms\TableRoom::class,
|
|
||||||
'redis' => SwooleTW\Http\Websocket\Rooms\RedisRoom::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Room drivers settings
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
'settings' => [
|
|
||||||
|
|
||||||
'table' => [
|
|
||||||
'room_rows' => 4096,
|
|
||||||
'room_size' => 2048,
|
|
||||||
'client_rows' => 8192,
|
|
||||||
'client_size' => 2048,
|
|
||||||
],
|
|
||||||
|
|
||||||
'redis' => [
|
|
||||||
'server' => [
|
|
||||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
|
||||||
'password' => env('REDIS_PASSWORD', null),
|
|
||||||
'port' => env('REDIS_PORT', 6379),
|
|
||||||
'database' => 0,
|
|
||||||
'persistent' => true,
|
|
||||||
],
|
|
||||||
'options' => [
|
|
||||||
//
|
|
||||||
],
|
|
||||||
'prefix' => 'swoole:',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
@ -22,15 +22,17 @@ cd Xboard
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. 安装数据库:
|
2. 安装数据库:
|
||||||
```bash
|
|
||||||
# 快速安装(推荐新手使用)
|
|
||||||
docker compose run -it --rm \
|
|
||||||
-e enable_sqlite=true \
|
|
||||||
-e enable_redis=true \
|
|
||||||
-e admin_account=admin@demo.com \
|
|
||||||
web php artisan xboard:install
|
|
||||||
|
|
||||||
# 自定义配置安装(高级用户)
|
- 快速安装(推荐新手使用)
|
||||||
|
```bash
|
||||||
|
docker compose run -it --rm \
|
||||||
|
-e ENABLE_SQLITE=true \
|
||||||
|
-e ENABLE_REDIS=true \
|
||||||
|
-e ADMIN_ACCOUNT=admin@demo.com \
|
||||||
|
web php artisan xboard:install && \
|
||||||
|
```
|
||||||
|
- 自定义配置安装(高级用户)
|
||||||
|
```bash
|
||||||
docker compose run -it --rm web php artisan xboard:install
|
docker compose run -it --rm web php artisan xboard:install
|
||||||
```
|
```
|
||||||
> 安装完成后请保存返回的后台地址和管理员账号密码
|
> 安装完成后请保存返回的后台地址和管理员账号密码
|
||||||
|
Loading…
Reference in New Issue
Block a user