Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66f34d7514 | |||
| cab76f1cf3 | |||
| 99413c5962 | |||
| d231b6ebcd | |||
| 6c67cbf85a | |||
| 7acae3dcc4 | |||
| dff2e721cb | |||
| 3a05281a9f | |||
| 74d93691b9 | |||
| d743296392 | |||
| 024a9dfb54 | |||
| d3bc8ff8fe | |||
| ea6a2d8fc6 | |||
| f6af7313d0 | |||
| 4877866fe8 | |||
| 18c90a0aee | |||
| 4d2b442885 | |||
| a550fd1436 | |||
| 43bac89d3a | |||
| d57c3ca60d | |||
| 558834b154 | |||
| e2262f1435 | |||
| a60b23b17d | |||
| 6aee3ea40e | |||
| 89d2aed46d | |||
| cf44a62db7 | |||
| 5b3474a06d | |||
| dde41da139 | |||
| cddcb144ef | |||
| ae249f8e96 | |||
| 9bc1d7a286 | |||
| 256ca28e00 | |||
| d6b7ae6404 | |||
| 654f1f84fb | |||
| aa3ff5cb66 | |||
| 22ffe0dace | |||
| dee7525bb4 | |||
| cfc8a05cba | |||
| 1378fdb45b | |||
| c4595bc665 | |||
| 1d9cb2295c | |||
| ddac216e2d | |||
| e482b72430 | |||
| 6cfdd1c9b1 | |||
| 1d36069726 | |||
| 46656fe4f7 | |||
| 190e64b7c2 | |||
| 42542725f7 | |||
| ab34ef327a | |||
| 60ed240e66 | |||
| b0fcc9244a | |||
| acb40cc1f9 | |||
| f0c620cbc2 | |||
| 8cc247b653 | |||
| bab7ed8e97 | |||
| 0389edd4d0 | |||
| 338aad7f6c | |||
| fc283af60f | |||
| 9270d94668 | |||
| 5cc0b77982 | |||
| 261487437b |
@@ -38,4 +38,7 @@ server {
|
|||||||
location ~ /\.ht {
|
location ~ /\.ht {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log /dev/null crit;
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ directory=/www
|
|||||||
command=sh -c "chown -R www:www /www && chmod -R 775 /www"
|
command=sh -c "chown -R www:www /www && chmod -R 775 /www"
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=false
|
autorestart=false
|
||||||
|
priority=1
|
||||||
stdout_logfile=/dev/stdout
|
stdout_logfile=/dev/stdout
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_maxbytes=0
|
||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/dev/stderr
|
||||||
@@ -25,6 +26,7 @@ stderr_logfile_maxbytes=0
|
|||||||
[program:nginx]
|
[program:nginx]
|
||||||
command=nginx -g 'daemon off;'
|
command=nginx -g 'daemon off;'
|
||||||
user=root
|
user=root
|
||||||
|
priority=5
|
||||||
stdout_logfile=/dev/stdout
|
stdout_logfile=/dev/stdout
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_maxbytes=0
|
||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/dev/stderr
|
||||||
@@ -36,6 +38,7 @@ startretries=10
|
|||||||
[program:cron]
|
[program:cron]
|
||||||
command=crond -f -l 8
|
command=crond -f -l 8
|
||||||
user=root
|
user=root
|
||||||
|
priority=4
|
||||||
stdout_logfile=/dev/stdout
|
stdout_logfile=/dev/stdout
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_maxbytes=0
|
||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/dev/stderr
|
||||||
@@ -62,6 +65,8 @@ command=php -c php.ini webman.php start
|
|||||||
directory=/www
|
directory=/www
|
||||||
user=www
|
user=www
|
||||||
numprocs=1
|
numprocs=1
|
||||||
|
priority=2
|
||||||
|
startsecs=3
|
||||||
stdout_logfile=/dev/stdout
|
stdout_logfile=/dev/stdout
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_maxbytes=0
|
||||||
stderr_logfile=/dev/stderr
|
stderr_logfile=/dev/stderr
|
||||||
@@ -74,6 +79,7 @@ startretries=10
|
|||||||
command=php artisan horizon
|
command=php artisan horizon
|
||||||
directory=/www
|
directory=/www
|
||||||
user=www
|
user=www
|
||||||
|
priority=3
|
||||||
stdout_logfile=/www/storage/logs/queue.log
|
stdout_logfile=/www/storage/logs/queue.log
|
||||||
stdout_logfile_maxbytes=0
|
stdout_logfile_maxbytes=0
|
||||||
stderr_logfile=/www/storage/logs/queue_error.log
|
stderr_logfile=/www/storage/logs/queue_error.log
|
||||||
|
|||||||
Executable → Regular
@@ -1,89 +1,94 @@
|
|||||||
name: Docker
|
name: Docker Build and Publish
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "dev" ]
|
branches: ["legacy", "dev"]
|
||||||
# Publish semver tags as releases.
|
workflow_dispatch:
|
||||||
tags: [ 'v*.*.*' ]
|
|
||||||
workflow_dispatch: # Enable manual trigger
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Use docker.io for Docker Hub if empty
|
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
# github.repository as <account>/<repo>
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
# This is used to complete the identity challenge
|
|
||||||
# with sigstore/fulcio when running outside of PRs.
|
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: satackey/action-docker-layer-caching@v0.0.11
|
with:
|
||||||
continue-on-error: true
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,amd64'
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
driver-opts: |
|
||||||
|
image=moby/buildkit:latest
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=sha,format=long
|
||||||
|
type=raw,value=new
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: get_version
|
||||||
|
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Update version in app.php
|
||||||
|
run: |
|
||||||
|
VERSION=$(date '+%Y%m%d')-$(git rev-parse --short HEAD)
|
||||||
|
sed -i "s/'version' => '.*'/'version' => '$VERSION'/g" config/app.php
|
||||||
|
echo "Updated version to: $VERSION"
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:legacy
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:dev
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:latest
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:${{ steps.get_version.outputs.version }}
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_INLINE_CACHE=1
|
||||||
|
provenance: false
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@v3.4.0
|
uses: sigstore/cosign-installer@v3.4.0
|
||||||
with:
|
with:
|
||||||
cosign-release: 'v2.2.2'
|
cosign-release: 'v2.2.2'
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Sign image
|
||||||
uses: docker/setup-buildx-action@v3.2.0
|
if: steps.build-and-push.outputs.digest != ''
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
|
||||||
uses: docker/login-action@v3.1.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5.5.1
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: get_version
|
|
||||||
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@v5.3.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:latest,${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard,${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:${{ steps.get_version.outputs.version }}
|
|
||||||
# Sign the resulting Docker image digest except on PRs.
|
|
||||||
# This will only write to the public Rekor transparency log when the Docker
|
|
||||||
# repository is public to avoid leaking data. If you would like to publish
|
|
||||||
# transparency data even for private images, pass --force to cosign below.
|
|
||||||
# https://github.com/sigstore/cosign
|
|
||||||
- name: Sign the published Docker image
|
|
||||||
env:
|
env:
|
||||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
COSIGN_EXPERIMENTAL: 1
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
run: |
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes "{}@${{ steps.build-and-push.outputs.digest }}"
|
||||||
# This step uses the identity token to provision an ephemeral certificate
|
|
||||||
# against the sigstore community Fulcio instance.
|
|
||||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
|
||||||
|
|||||||
+2
-1
@@ -11,7 +11,8 @@ COPY .docker /
|
|||||||
COPY . /www
|
COPY . /www
|
||||||
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 \
|
||||||
|
&& cp /www/.env.example /www/.env \
|
||||||
&& chown -R www:www /www \
|
&& chown -R www:www /www \
|
||||||
&& chmod -R 775 /www
|
&& chmod -R 775 /www
|
||||||
|
|
||||||
CMD /usr/bin/supervisord --nodaemon -c /etc/supervisor/supervisord.conf
|
CMD ["/usr/bin/supervisord", "--nodaemon", "-c", "/etc/supervisor/supervisord.conf"]
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
# 关于Xboard
|
# About Xboard
|
||||||
Xboard是基于V2board二次开发,在性能上和功能上都有大部分增强的**面板
|
Xboard is a panel based on V2board's secondary development, with significant enhancements in both performance and functionality.
|
||||||
|
|
||||||
# 免责声明
|
# Disclaimer
|
||||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
This project is personally developed and maintained by me for learning purposes. I do not guarantee any availability and am not responsible for any consequences resulting from the use of this software.
|
||||||
|
|
||||||
# Xboard 特点
|
# Xboard Features
|
||||||
基于V2board 二次开发,增加了以下特性
|
Based on V2board's secondary development, with the following added features:
|
||||||
- 升级Laravel10
|
- Upgraded to Laravel 10
|
||||||
- 适配Laravels (提升至10+倍并发)
|
- Adapted to Laravels (10+ times concurrent improvement)
|
||||||
- 适配Webman (比laravels快50%左右)
|
- Adapted to Webman (about 50% faster than laravels)
|
||||||
- 修改配置从数据库中获取
|
- Modified configuration retrieval from database
|
||||||
- 支持Docker部署、分布式部署
|
- Support for Docker deployment and distributed deployment
|
||||||
- 支持根据用户IP归属地来下发订阅
|
- Support for subscription distribution based on user IP location
|
||||||
- 增加Hy2支持
|
- Added Hy2 support
|
||||||
- 增加sing-box下发
|
- Added sing-box distribution
|
||||||
- 支持直接从cloudflare获取访问者真实IP
|
- Support for obtaining real visitor IP directly from Cloudflare
|
||||||
- 支持根据客户端版本自动下发新协议
|
- Support for automatic new protocol distribution based on client version
|
||||||
- 支持线路筛选(订阅地址后面增加 &filter=香港|美国)
|
- Support for route filtering (add &filter=HongKong|USA after subscription URL)
|
||||||
- 支持Sqlite安装(代替Mysql,自用用户福音)
|
- Support for Sqlite installation (alternative to MySQL, great for personal use)
|
||||||
- 使用Vue3 + TypeScript + NaiveUI + Unocss + Pinia重构用户前端
|
- User frontend rebuilt using Vue3 + TypeScript + NaiveUI + Unocss + Pinia
|
||||||
- 修复大量BUG
|
- Fixed numerous bugs
|
||||||
|
|
||||||
# **系统架构**
|
# **System Architecture**
|
||||||
|
|
||||||
- PHP8.1+
|
- PHP8.1+
|
||||||
- Composer
|
- Composer
|
||||||
@@ -29,36 +29,36 @@ Xboard是基于V2board二次开发,在性能上和功能上都有大部分增
|
|||||||
- Redis
|
- Redis
|
||||||
- Laravel
|
- Laravel
|
||||||
|
|
||||||
## 性能对比 [查看详情](./docs/性能对比.md)
|
## Performance Comparison [View Details](./docs/性能对比.md)
|
||||||
> xboard 无论前端还是后端性能都有巨大的提升
|
> xboard shows tremendous performance improvements in both frontend and backend
|
||||||
|
|
||||||
|场景 | php-fpm(传统) | php-fpm(传统开启opcache) | laravels | webman(docker)|
|
|Scenario | php-fpm(traditional) | php-fpm(traditional with opcache) | laravels | webman(docker)|
|
||||||
|---- | ---- |---- |----| ---|
|
|---- | ---- |---- |---- | ---|
|
||||||
|首页 | 6请求/秒 | 157请求/秒 | 477请求/秒 | 803请求/秒 |
|
|Homepage | 6 req/s | 157 req/s | 477 req/s| 803 req/s|
|
||||||
|用户订阅 | 6请求/秒 | 196请求/秒 | 586请求/秒 | 1064请求/秒 |
|
|User Subscription| 6 req/s | 196 req/s | 586 req/s| 1064 req/s|
|
||||||
|用户首页延迟| 308ms | 110ms | 101ms | 98ms |
|
|User Homepage Latency| 308ms | 110ms | 101ms | 98ms|
|
||||||
|
|
||||||
## 页面展示
|
## Page Display
|
||||||

|

|
||||||
|
|
||||||
## 安装 / 更新 / 回滚
|
## Installation / Update / Rollback
|
||||||
你可以点击查看下列方式的**安装、更新**步骤
|
You can click to view the **installation and update** steps for the following methods:
|
||||||
- [1panel 部署](./docs/1panel安装指南.md)
|
- [1panel Deployment](./docs/1panel安装指南.md)
|
||||||
- [Docker Compose 纯命令行快速部署](./docs/docker-compose安装指南.md)
|
- [Docker Compose Command-line Quick Deployment](./docs/docker-compose安装指南.md)
|
||||||
- [aapanel + Docker Compose (推荐)](./docs/aapanel+docker安装指南.md)
|
- [aapanel + Docker Compose (Recommended)](./docs/aapanel+docker安装指南.md)
|
||||||
- [aapanel 部署](./docs/aapanel安装指南.md)
|
- [aapanel Deployment](./docs/aapanel安装指南.md)
|
||||||
### 从其他版本迁移
|
|
||||||
#### 数据库迁移
|
|
||||||
**根据你的版本查看对应的迁移指南进行迁移**
|
|
||||||
- v2board dev 23/10/27的版本 [点击跳转迁移指南](./docs/v2b_dev迁移指南.md)
|
|
||||||
- v2board 1.7.4 [点击跳转迁移指南](./docs/v2b_1.7.4迁移指南.md)
|
|
||||||
- v2board 1.7.3 [点击跳转迁移指南](./docs/v2b_1.7.3迁移指南.md)
|
|
||||||
- v2board wyx2685 [点击跳转迁移指南](./docs/v2b_wyx2685迁移指南.md)
|
|
||||||
|
|
||||||
|
### Migrating from Other Versions
|
||||||
|
#### Database Migration
|
||||||
|
**Check the corresponding migration guide according to your version**
|
||||||
|
- v2board dev version 23/10/27 [Jump to Migration Guide](./docs/v2b_dev迁移指南.md)
|
||||||
|
- v2board 1.7.4 [Jump to Migration Guide](./docs/v2b_1.7.4迁移指南.md)
|
||||||
|
- v2board 1.7.3 [Jump to Migration Guide](./docs/v2b_1.7.3迁移指南.md)
|
||||||
|
- v2board wyx2685 [Jump to Migration Guide](./docs/v2b_wyx2685迁移指南.md)
|
||||||
|
|
||||||
### 注意
|
### Note
|
||||||
> 修改后台路径需要重启才能生效
|
> Modifying the admin path requires a restart to take effect
|
||||||
```
|
```
|
||||||
docker compose restart
|
docker compose restart
|
||||||
```
|
```
|
||||||
> 如果是是aapanel安装则需要重启 webman守护进程
|
> If using aapanel installation, you need to restart the webman daemon process
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ class BackupDatabase extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 数据库备份逻辑
|
// 数据库备份逻辑
|
||||||
|
$databaseBackupPath = storage_path('backup/' . now()->format('Y-m-d_H-i-s') . '_' . config('database.connections.mysql.database') . '_database_backup.sql');
|
||||||
|
$compressedBackupPath = $databaseBackupPath . '.gz';
|
||||||
try{
|
try{
|
||||||
if (config('database.default') === 'mysql'){
|
if (config('database.default') === 'mysql'){
|
||||||
$databaseBackupPath = storage_path('backup/' . now()->format('Y-m-d_H-i-s') . '_' . config('database.connections.mysql.database') . '_database_backup.sql');
|
|
||||||
$this->info("1️⃣:开始备份Mysql");
|
$this->info("1️⃣:开始备份Mysql");
|
||||||
\Spatie\DbDumper\Databases\MySql::create()
|
\Spatie\DbDumper\Databases\MySql::create()
|
||||||
->setHost(config('database.connections.mysql.host'))
|
->setHost(config('database.connections.mysql.host'))
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ class MigrateFromV2b extends Command
|
|||||||
],
|
],
|
||||||
'1.7.3' => [
|
'1.7.3' => [
|
||||||
'ALTER TABLE `v2_stat_order` RENAME TO `v2_stat`;',
|
'ALTER TABLE `v2_stat_order` RENAME TO `v2_stat`;',
|
||||||
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount order_total INT COMMENT '订单合计';",
|
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount paid_total INT COMMENT '订单合计';",
|
||||||
|
"ALTER TABLE `v2_stat` CHANGE COLUMN order_count paid_count INT COMMENT '邀请佣金';",
|
||||||
"ALTER TABLE `v2_stat` CHANGE COLUMN commission_amount commission_total INT COMMENT '佣金合计';",
|
"ALTER TABLE `v2_stat` CHANGE COLUMN commission_amount commission_total INT COMMENT '佣金合计';",
|
||||||
"ALTER TABLE `v2_stat`
|
"ALTER TABLE `v2_stat`
|
||||||
ADD COLUMN paid_count INT NULL,
|
ADD COLUMN order_count INT NULL,
|
||||||
ADD COLUMN paid_total INT NULL,
|
ADD COLUMN order_total INT NULL,
|
||||||
ADD COLUMN register_count INT NULL,
|
ADD COLUMN register_count INT NULL,
|
||||||
ADD COLUMN invite_count INT NULL,
|
ADD COLUMN invite_count INT NULL,
|
||||||
ADD COLUMN transfer_used_total VARCHAR(32) NULL;
|
ADD COLUMN transfer_used_total VARCHAR(32) NULL;
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ class Server
|
|||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next, $node_type = null)
|
public function handle(Request $request, Closure $next, $node_type = null)
|
||||||
{
|
{
|
||||||
|
$token = $request->input('token');
|
||||||
|
if (empty($token)) {
|
||||||
|
throw new ApiException('token is null',403);
|
||||||
|
}
|
||||||
|
|
||||||
// alias
|
// alias
|
||||||
$aliasTypes = [
|
$aliasTypes = [
|
||||||
'v2ray' => 'vmess',
|
'v2ray' => 'vmess',
|
||||||
|
|||||||
@@ -76,14 +76,13 @@ class BTCPay
|
|||||||
//NOT BTCPay-Sig
|
//NOT BTCPay-Sig
|
||||||
//API doc is WRONG!
|
//API doc is WRONG!
|
||||||
$headerName = 'Btcpay-Sig';
|
$headerName = 'Btcpay-Sig';
|
||||||
$signraturHeader = isset($headers[$headerName]) ? $headers[$headerName] : '';
|
$signatureHeader = isset($headers[$headerName]) ? $headers[$headerName] : '';
|
||||||
$json_param = json_decode($payload, true);
|
$json_param = json_decode($payload, true);
|
||||||
|
|
||||||
$computedSignature = "sha256=" . \hash_hmac('sha256', $payload, $this->config['btcpay_webhook_key']);
|
$computedSignature = "sha256=" . \hash_hmac('sha256', $payload, $this->config['btcpay_webhook_key']);
|
||||||
|
|
||||||
if (!self::hashEqual($signraturHeader, $computedSignature)) {
|
if (!self::hashEqual($signatureHeader, $computedSignature)) {
|
||||||
throw new ApiException('HMAC signature does not match', 400);
|
throw new ApiException('HMAC signature does not match', 400);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//get order id store in metadata
|
//get order id store in metadata
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自己写别抄,抄NMB抄
|
||||||
|
*/
|
||||||
|
namespace App\Payments;
|
||||||
|
use App\Exceptions\ApiException;
|
||||||
|
|
||||||
|
class StripeALLInOne {
|
||||||
|
public function __construct($config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'currency' => [
|
||||||
|
'label' => '货币单位',
|
||||||
|
'description' => '请使用符合ISO 4217标准的三位字母,例如GBP',
|
||||||
|
'type' => 'input',
|
||||||
|
],
|
||||||
|
'stripe_sk_live' => [
|
||||||
|
'label' => 'SK_LIVE',
|
||||||
|
'description' => '',
|
||||||
|
'type' => 'input',
|
||||||
|
],
|
||||||
|
'stripe_webhook_key' => [
|
||||||
|
'label' => 'WebHook密钥签名',
|
||||||
|
'description' => 'whsec_....',
|
||||||
|
'type' => 'input',
|
||||||
|
],
|
||||||
|
'description' => [
|
||||||
|
'label' => '自定义商品介绍',
|
||||||
|
'description' => '',
|
||||||
|
'type' => 'input',
|
||||||
|
],
|
||||||
|
'payment_method' => [
|
||||||
|
'label' => '支付方式',
|
||||||
|
'description' => '请输入alipay, wechat_pay, cards',
|
||||||
|
'type' => 'input',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pay($order)
|
||||||
|
{
|
||||||
|
$currency = $this->config['currency'];
|
||||||
|
$exchange = $this->exchange('CNY', strtoupper($currency));
|
||||||
|
if (!$exchange) {
|
||||||
|
throw new ApiException('Currency conversion has timed out, please try again later', 500);
|
||||||
|
}
|
||||||
|
//jump url
|
||||||
|
$jumpUrl = null;
|
||||||
|
$actionType = 0;
|
||||||
|
$stripe = new \Stripe\StripeClient($this->config['stripe_sk_live']);
|
||||||
|
|
||||||
|
if ($this->config['payment_method'] != "cards"){
|
||||||
|
$stripePaymentMethod = $stripe->paymentMethods->create([
|
||||||
|
'type' => $this->config['payment_method'],
|
||||||
|
]);
|
||||||
|
// 准备支付意图的基础参数
|
||||||
|
$params = [
|
||||||
|
'amount' => floor($order['total_amount'] * $exchange),
|
||||||
|
'currency' => $currency,
|
||||||
|
'confirm' => true,
|
||||||
|
'payment_method' => $stripePaymentMethod->id,
|
||||||
|
'automatic_payment_methods' => ['enabled' => true],
|
||||||
|
'statement_descriptor_suffix' => 'sub-' . $order['user_id'] . '-' . substr($order['trade_no'], -8),
|
||||||
|
'description' => $this->config['description'],
|
||||||
|
'metadata' => [
|
||||||
|
'user_id' => $order['user_id'],
|
||||||
|
'out_trade_no' => $order['trade_no'],
|
||||||
|
'identifier' => ''
|
||||||
|
],
|
||||||
|
'return_url' => $order['return_url']
|
||||||
|
];
|
||||||
|
|
||||||
|
// 如果支付方式为 wechat_pay,添加相应的支付方式选项
|
||||||
|
if ($this->config['payment_method'] === 'wechat_pay') {
|
||||||
|
$params['payment_method_options'] = [
|
||||||
|
'wechat_pay' => [
|
||||||
|
'client' => 'web'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
//更新支持最新的paymentIntents方法,Sources API将在今年被彻底替
|
||||||
|
$stripeIntents = $stripe->paymentIntents->create($params);
|
||||||
|
|
||||||
|
$nextAction = null;
|
||||||
|
|
||||||
|
if (!$stripeIntents['next_action']) {
|
||||||
|
throw new ApiException(__('Payment gateway request failed'));
|
||||||
|
}else {
|
||||||
|
$nextAction = $stripeIntents['next_action'];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->config['payment_method']){
|
||||||
|
case "alipay":
|
||||||
|
if (isset($nextAction['alipay_handle_redirect'])){
|
||||||
|
$jumpUrl = $nextAction['alipay_handle_redirect']['url'];
|
||||||
|
$actionType = 1;
|
||||||
|
}else {
|
||||||
|
throw new ApiException('unable get Alipay redirect url', 500);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "wechat_pay":
|
||||||
|
if (isset($nextAction['wechat_pay_display_qr_code'])){
|
||||||
|
$jumpUrl = $nextAction['wechat_pay_display_qr_code']['data'];
|
||||||
|
}else {
|
||||||
|
throw new ApiException('unable get WeChat Pay redirect url', 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$creditCheckOut = $stripe->checkout->sessions->create([
|
||||||
|
'success_url' => $order['return_url'],
|
||||||
|
'client_reference_id' => $order['trade_no'],
|
||||||
|
'payment_method_types' => ['card'],
|
||||||
|
'line_items' => [
|
||||||
|
[
|
||||||
|
'price_data' => [
|
||||||
|
'currency' => $currency,
|
||||||
|
'unit_amount' => floor($order['total_amount'] * $exchange),
|
||||||
|
'product_data' => [
|
||||||
|
'name' => 'sub-' . $order['user_id'] . '-' . substr($order['trade_no'], -8),
|
||||||
|
'description' => $this->config['description'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'quantity' => 1,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'mode' => 'payment',
|
||||||
|
]);
|
||||||
|
$jumpUrl = $creditCheckOut['url'];
|
||||||
|
$actionType = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $actionType,
|
||||||
|
'data' => $jumpUrl
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notify($params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
\Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
|
||||||
|
//Workerman不支持使用php://input, stripe同时要求验证签名的payload不能经过修改,所以使用这个方法
|
||||||
|
$payload = $GLOBALS['HTTP_RAW_POST_DATA'];
|
||||||
|
$headers = getallheaders();
|
||||||
|
$headerName = 'Stripe-Signature';
|
||||||
|
$signatureHeader = $headers[$headerName] ?? '';
|
||||||
|
$event = \Stripe\Webhook::constructEvent(
|
||||||
|
$payload,
|
||||||
|
$signatureHeader,
|
||||||
|
$this->config['stripe_webhook_key']
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (\UnexpectedValueException $e){
|
||||||
|
throw new ApiException('Error parsing payload', 400);
|
||||||
|
}
|
||||||
|
catch (\Stripe\Exception\SignatureVerificationException $e) {
|
||||||
|
throw new ApiException('signature not match', 400);
|
||||||
|
}
|
||||||
|
switch ($event->type) {
|
||||||
|
case 'payment_intent.succeeded':
|
||||||
|
$object = $event->data->object;
|
||||||
|
if ($object->status === 'succeeded') {
|
||||||
|
if (!isset($object->metadata->out_trade_no)) {
|
||||||
|
return('order error');
|
||||||
|
}
|
||||||
|
$metaData = $object->metadata;
|
||||||
|
$tradeNo = $metaData->out_trade_no;
|
||||||
|
return [
|
||||||
|
'trade_no' => $tradeNo,
|
||||||
|
'callback_no' => $object->id
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'checkout.session.completed':
|
||||||
|
$object = $event->data->object;
|
||||||
|
if ($object->payment_status === 'paid') {
|
||||||
|
return [
|
||||||
|
'trade_no' => $object->client_reference_id,
|
||||||
|
'callback_no' => $object->payment_intent
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'checkout.session.async_payment_succeeded':
|
||||||
|
$object = $event->data->object;
|
||||||
|
return [
|
||||||
|
'trade_no' => $object->client_reference_id,
|
||||||
|
'callback_no' => $object->payment_intent
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ApiException('event is not support');
|
||||||
|
}
|
||||||
|
return('success');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function exchange($from, $to)
|
||||||
|
{
|
||||||
|
$from = strtolower($from);
|
||||||
|
$to = strtolower($to);
|
||||||
|
$result = file_get_contents("https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/" . $from . ".min.json");
|
||||||
|
$result = json_decode($result, true);
|
||||||
|
return $result[$from][$to];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use App\Plugins\Telegram\Telegram;
|
|||||||
|
|
||||||
class GetLatestUrl extends Telegram {
|
class GetLatestUrl extends Telegram {
|
||||||
public $command = '/getlatesturl';
|
public $command = '/getlatesturl';
|
||||||
public $description = '将Telegram账号绑定到网站';
|
public $description = '获取网站最新网址';
|
||||||
|
|
||||||
public function handle($message, $match = []) {
|
public function handle($message, $match = []) {
|
||||||
$telegramService = $this->telegramService;
|
$telegramService = $this->telegramService;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Plugins\Telegram\Commands;
|
||||||
|
|
||||||
|
use App\Plugins\Telegram\Telegram;
|
||||||
|
|
||||||
|
class Start extends Telegram {
|
||||||
|
public $command = '/start';
|
||||||
|
public $description = 'telegram机器人初始化';
|
||||||
|
|
||||||
|
public function handle($message, $match = []) {
|
||||||
|
if (!$message->is_private) return;
|
||||||
|
$telegramService = $this->telegramService;
|
||||||
|
$text = "/start 显示所有可用指令\n /bind+空格+订阅链接,将telegram绑定至账户\n /traffic 获取当前使用流量 \n /getlatesturl 获取网站最新网址 \n /unbind 解绑telegram账户";
|
||||||
|
$telegramService->sendMessage($message->chat_id, $text, 'markdown');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,17 @@ class UnBind extends Telegram {
|
|||||||
|
|
||||||
public function handle($message, $match = []) {
|
public function handle($message, $match = []) {
|
||||||
if (!$message->is_private) return;
|
if (!$message->is_private) return;
|
||||||
$user = User::where('telegram_id', $message->chat_id)->first();
|
if (!isset($message->args[0])) {
|
||||||
$telegramService = $this->telegramService;
|
$user = User::where('telegram_id', $message->chat_id)->first();
|
||||||
if (!$user) {
|
} else {
|
||||||
$telegramService->sendMessage($message->chat_id, '没有查询到您的用户信息,请先绑定账号', 'markdown');
|
$chat = User::where('telegram_id', $message->chat_id)->first();
|
||||||
return;
|
if (!$chat) return;
|
||||||
|
if (!($chat->is_admin || $chat->is_staff)) return;
|
||||||
|
if (strpos($message->args[0], '@') !== true) {
|
||||||
|
$user = User::where('email', $message->args[0])->first();
|
||||||
|
} else {
|
||||||
|
$user = User::where('telegram_id', $message->args[0])->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$user->telegram_id = NULL;
|
$user->telegram_id = NULL;
|
||||||
if (!$user->save()) {
|
if (!$user->save()) {
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ class ClashMeta
|
|||||||
return response($yaml, 200)
|
return response($yaml, 200)
|
||||||
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||||
->header('profile-update-interval', '24')
|
->header('profile-update-interval', '24')
|
||||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName))
|
||||||
|
->header('profile-web-page-url', admin_setting('app_url'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,8 +195,6 @@ class ClashMeta
|
|||||||
$array['server'] = $server['host'];
|
$array['server'] = $server['host'];
|
||||||
$array['port'] = $server['port'];
|
$array['port'] = $server['port'];
|
||||||
$array['uuid'] = $password;
|
$array['uuid'] = $password;
|
||||||
$array['alterId'] = 0;
|
|
||||||
$array['cipher'] = 'auto';
|
|
||||||
$array['udp'] = true;
|
$array['udp'] = true;
|
||||||
|
|
||||||
// XTLS流控算法
|
// XTLS流控算法
|
||||||
@@ -242,10 +241,8 @@ class ClashMeta
|
|||||||
$array['ws-opts']['path'] = $wsSettings['path'];
|
$array['ws-opts']['path'] = $wsSettings['path'];
|
||||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||||
$array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
|
$array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
|
||||||
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
$array['ws-opts']['max-early-data'] = 2560;
|
||||||
$array['ws-path'] = $wsSettings['path'];
|
$array['ws-opts']['early-data-header-name'] = 'Sec-WebSocket-Protocol';
|
||||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
|
||||||
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($server['network'] === 'grpc') {
|
if ($server['network'] === 'grpc') {
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class Shadowrocket
|
|||||||
if ($server['network_settings']) {
|
if ($server['network_settings']) {
|
||||||
$wsSettings = $server['network_settings'];
|
$wsSettings = $server['network_settings'];
|
||||||
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
||||||
$config['path'] = $wsSettings['path'];
|
$config['path'] = $wsSettings['path'] . '?ed=2560';
|
||||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||||
$config['obfsParam'] = $wsSettings['headers']['Host'];
|
$config['obfsParam'] = $wsSettings['headers']['Host'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class SingBox
|
|||||||
$array['transport']['path'] = $wsSettings['path'];
|
$array['transport']['path'] = $wsSettings['path'];
|
||||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||||
$array['transport']['headers'] = ['Host' => array($wsSettings['headers']['Host'])];
|
$array['transport']['headers'] = ['Host' => array($wsSettings['headers']['Host'])];
|
||||||
$array['transport']['max_early_data'] = 2048;
|
$array['transport']['max_early_data'] = 2560;
|
||||||
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ class SingBox
|
|||||||
$array['transport']['path'] = $wsSettings['path'];
|
$array['transport']['path'] = $wsSettings['path'];
|
||||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||||
$array['transport']['headers'] = ['Host' => array($wsSettings['headers']['Host'])];
|
$array['transport']['headers'] = ['Host' => array($wsSettings['headers']['Host'])];
|
||||||
$array['transport']['max_early_data'] = 2048;
|
$array['transport']['max_early_data'] = 2560;
|
||||||
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ class SingBox
|
|||||||
if (isset($server['network_settings']['headers']['Host'])) {
|
if (isset($server['network_settings']['headers']['Host'])) {
|
||||||
$array['transport']['headers'] = ['Host' => array($server['network_settings']['headers']['Host'])];
|
$array['transport']['headers'] = ['Host' => array($server['network_settings']['headers']['Host'])];
|
||||||
}
|
}
|
||||||
$array['transport']['max_early_data'] = 2048;
|
$array['transport']['max_early_data'] = 2560;
|
||||||
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
$array['transport']['early_data_header_name'] = 'Sec-WebSocket-Protocol';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ class Surge
|
|||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'aes-192-gcm',
|
'aes-192-gcm',
|
||||||
'aes-256-gcm',
|
'aes-256-gcm',
|
||||||
'chacha20-ietf-poly1305'
|
'chacha20-ietf-poly1305',
|
||||||
|
'2022-blake3-aes-128-gcm',
|
||||||
|
'2022-blake3-aes-256-gcm',
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
// [Proxy]
|
// [Proxy]
|
||||||
|
|||||||
@@ -110,11 +110,20 @@ class Helper
|
|||||||
|
|
||||||
public static function getSubscribeUrl(string $token, $subscribeUrl = null)
|
public static function getSubscribeUrl(string $token, $subscribeUrl = null)
|
||||||
{
|
{
|
||||||
|
$strs = 'QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm';
|
||||||
|
$randstr = substr(str_shuffle($strs), 0, rand(4,8));
|
||||||
|
|
||||||
$path = route('client.subscribe', ['token' => $token], false);
|
$path = route('client.subscribe', ['token' => $token], false);
|
||||||
if(!$subscribeUrl){
|
if(!$subscribeUrl){
|
||||||
$subscribeUrls = explode(',', admin_setting('subscribe_url'));
|
$subscribeUrls = explode(',', admin_setting('subscribe_url'));
|
||||||
$subscribeUrl = \Arr::random($subscribeUrls);
|
$subscribeUrl = \Arr::random($subscribeUrls);
|
||||||
$subscribeUrl = self::replaceByPattern($subscribeUrl);
|
$subscribeUrl = self::replaceByPattern($subscribeUrl);
|
||||||
|
if (strpos($subscribeUrl, "*") !== false) {
|
||||||
|
$subscribeUrl = str_replace("*", $randstr, $subscribeUrl);
|
||||||
|
} elseif (strpos($subscribeUrl, '{uuid}') !== false) {
|
||||||
|
$user = User::where('token', $token)->first();
|
||||||
|
$subscribeUrl = str_replace('{uuid}', $user->uuid, $subscribeUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $subscribeUrl ? rtrim($subscribeUrl, '/') . $path : url($path);
|
return $subscribeUrl ? rtrim($subscribeUrl, '/') . $path : url($path);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
"paragonie/sodium_compat": "^1.20",
|
"paragonie/sodium_compat": "^1.20",
|
||||||
"php-curl-class/php-curl-class": "^8.6",
|
"php-curl-class/php-curl-class": "^8.6",
|
||||||
"spatie/db-dumper": "^3.4",
|
"spatie/db-dumper": "^3.4",
|
||||||
"stripe/stripe-php": "^7.36.1",
|
"stripe/stripe-php": "^v14.9.0",
|
||||||
"symfony/http-client": "^6.4",
|
"symfony/http-client": "^6.4",
|
||||||
"symfony/mailgun-mailer": "^6.4",
|
"symfony/mailgun-mailer": "^6.4",
|
||||||
"symfony/yaml": "*",
|
"symfony/yaml": "*",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ services:
|
|||||||
xboard:
|
xboard:
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
image: ghcr.io/cedar2025/xboard:latest
|
image: ghcr.io/cedar2025/xboard:legacy
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/www/
|
- ./:/www/
|
||||||
# - ./.env:/www/.env
|
# - ./.env:/www/.env
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ rm -rf .htaccess 404.html 502.html index.html .user.ini
|
|||||||
```
|
```
|
||||||
> 执行命令从 Github 克隆到当前目录。
|
> 执行命令从 Github 克隆到当前目录。
|
||||||
```
|
```
|
||||||
git clone https://github.com/cedar2025/Xboard.git ./
|
git clone -b dev https://github.com/cedar2025/Xboard.git ./
|
||||||
```
|
```
|
||||||
> 复制一份docker-compose.yaml文件
|
> 复制一份docker-compose.yaml文件
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ rm -rf .htaccess 404.html 502.html index.html .user.ini
|
|||||||
```
|
```
|
||||||
> 执行命令从 Github 克隆到当前目录。
|
> 执行命令从 Github 克隆到当前目录。
|
||||||
```
|
```
|
||||||
git clone https://github.com/cedar2025/Xboard.git ./
|
git clone -b dev https://github.com/cedar2025/Xboard.git ./
|
||||||
```
|
```
|
||||||
> 执行命令安装依赖包以及V2board
|
> 执行命令安装依赖包以及V2board
|
||||||
```
|
```
|
||||||
|
|||||||
+92
-50
@@ -1,77 +1,119 @@
|
|||||||
## Docker-Compose 部署教程
|
## Docker-Compose 快速部署指南
|
||||||
本文教你如何在命令行使用docker-compose + sqlite来快速部署Xboard
|
|
||||||
如果你需要使用Mysql,你需要自行处理好Mysql的安装。
|
### 环境要求
|
||||||
### 部署 (使用docker-compose 2分钟部署)
|
- Docker (最新稳定版)
|
||||||
> 在此提供Xboard安装、快速体验Xboard的步骤。
|
- 至少 1GB 可用内存
|
||||||
使用docker compose + sqlite 快速部署站点(**无需安装Mysql以及redis**)
|
- 至少 10GB 可用磁盘空间
|
||||||
1. 安装docker
|
- 系统支持: Linux/macOS/Windows
|
||||||
```
|
- 开放端口: 7001 (默认)
|
||||||
|
|
||||||
|
### 部署步骤
|
||||||
|
|
||||||
|
#### 1. 安装 Docker
|
||||||
|
```bash
|
||||||
|
# 安装 Docker
|
||||||
curl -sSL https://get.docker.com | bash
|
curl -sSL https://get.docker.com | bash
|
||||||
```
|
|
||||||
Centos系统可能需要执行下面命令来启动Docker。
|
# CentOS 系统需要执行以下命令启动 Docker
|
||||||
```
|
|
||||||
systemctl enable docker
|
systemctl enable docker
|
||||||
systemctl start docker
|
systemctl start docker
|
||||||
```
|
```
|
||||||
2. 获取Docker compose 文件
|
|
||||||
```
|
#### 2. 获取部署文件
|
||||||
git clone -b docker-compose --depth 1 https://github.com/cedar2025/Xboard
|
```bash
|
||||||
|
git clone -b docker-compose --depth 1 https://github.com/cedar2025/Xboard
|
||||||
cd Xboard
|
cd Xboard
|
||||||
```
|
```
|
||||||
3. 执行数据库安装命令
|
|
||||||
> 选择 **启用sqlite** 和 **Docker内置的Redis**
|
#### 3. 初始化安装
|
||||||
```
|
> 提供两种安装方式,选择其一即可:
|
||||||
docker compose run -it --rm -e enable_sqlite=true -e enable_redis=true -e admin_account=your_admin_email@example.com xboard php artisan xboard:install
|
|
||||||
```
|
**方式一:快速安装** (推荐)
|
||||||
> 或者根据自己的需要在运行时选择
|
```bash
|
||||||
|
# 使用 SQLite + Docker内置Redis
|
||||||
|
docker compose run -it --rm \
|
||||||
|
-e enable_sqlite=true \
|
||||||
|
-e enable_redis=true \
|
||||||
|
-e admin_account=admin@demo.com \
|
||||||
|
xboard php artisan xboard:install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**方式二:自定义安装**
|
||||||
|
```bash
|
||||||
|
# 根据提示自定义配置
|
||||||
docker compose run -it --rm xboard php artisan xboard:install
|
docker compose run -it --rm xboard php artisan xboard:install
|
||||||
```
|
```
|
||||||
> 执行这条命令之后,会返回你的后台地址和管理员账号密码(你需要记录下来)
|
|
||||||
> 你需要执行下面的 **启动xborad** 步骤之后才能访问后台
|
|
||||||
|
|
||||||
4. 启动Xboard
|
> **重要提示:**
|
||||||
```
|
> - 安装完成后会显示后台地址和管理员账号密码,请务必保存
|
||||||
|
> - 如需使用 MySQL,请先自行安装并配置 MySQL 后再部署
|
||||||
|
|
||||||
|
#### 4. 启动服务
|
||||||
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
> 安装完成之后即可访问你的站点
|
|
||||||
5. 访问站点
|
|
||||||
> 启动之后网站端口默认为7001, 你可以配置nginx反向代理使用80端口
|
|
||||||
|
|
||||||
网站地址: http://你的IP:7001/
|
#### 5. 访问站点
|
||||||
在此你已经成功部署了, 你可以访问网址体验Xboard的完整功能,
|
- 网站地址:`http://服务器IP:7001`
|
||||||
|
- 后台地址:安装时提供的地址
|
||||||
|
|
||||||
> 如果你需要使用mysql,请自行安装Mysql后重新部署
|
### 更新指南
|
||||||
|
|
||||||
### **更新**
|
#### 方式一:快速更新(保持最新版本)
|
||||||
1. 修改版本
|
```bash
|
||||||
```
|
|
||||||
cd Xboard
|
cd Xboard
|
||||||
vi docker-compose.yaml
|
|
||||||
```
|
|
||||||
> 修改docker-compose.yaml 当中image后面的版本号为你需要的版本
|
|
||||||
> 如果为版本为latest 则可以忽略这一步,直接进行第二步
|
|
||||||
|
|
||||||
2. 更新数据库(可以执行多次都是安全的)
|
|
||||||
```
|
|
||||||
docker compose pull
|
docker compose pull
|
||||||
docker compose down
|
docker compose down
|
||||||
docker compose run -it --rm xboard php artisan xboard:update
|
docker compose run -it --rm xboard php artisan xboard:update
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
> 即可更新成功
|
|
||||||
|
|
||||||
### **回滚**
|
#### 方式二:更新至指定版本
|
||||||
> 此回滚不回滚数据库,是否回滚数据库请查看相关文档
|
1. 修改版本号
|
||||||
1. 回退版本
|
```bash
|
||||||
```
|
# 编辑 docker-compose.yaml,修改 image 的版本号
|
||||||
vi docker-compose.yaml
|
vi docker-compose.yaml
|
||||||
```
|
```
|
||||||
> 修改docker-compose.yaml 当中image后面的版本号为更新前的版本号
|
|
||||||
2. 启动
|
2. 执行更新
|
||||||
```
|
```bash
|
||||||
|
docker compose pull
|
||||||
|
docker compose down
|
||||||
|
docker compose run -it --rm xboard php artisan xboard:update
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注意
|
### 版本回滚
|
||||||
启用webman后做的任何代码修改都需要重启生效
|
```bash
|
||||||
|
# 1. 修改 docker-compose.yaml 中的版本号为目标版本
|
||||||
|
vi docker-compose.yaml
|
||||||
|
|
||||||
|
# 2. 重启服务
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **端口配置**
|
||||||
|
- 默认端口为 7001
|
||||||
|
- 可通过 Nginx 反向代理使用 80/443 端口
|
||||||
|
- 如需修改端口,请编辑 docker-compose.yaml
|
||||||
|
|
||||||
|
2. **数据持久化**
|
||||||
|
- 数据默认存储在 ./data 目录
|
||||||
|
- 建议定期备份 data 目录
|
||||||
|
|
||||||
|
3. **性能优化**
|
||||||
|
- 启用 webman 后的代码修改需要重启服务才能生效
|
||||||
|
- 可根据实际需求调整容器资源限制
|
||||||
|
|
||||||
|
### 安全建议
|
||||||
|
1. 及时更新到最新版本
|
||||||
|
2. 修改默认管理员账号
|
||||||
|
3. 使用强密码
|
||||||
|
4. 建议配置 SSL 证书
|
||||||
|
5. 定期备份数据
|
||||||
|
|
||||||
|
### 技术支持
|
||||||
|
- GitHub Issues: https://github.com/cedar2025/Xboard/issues
|
||||||
|
- 官方文档:[文档链接]
|
||||||
|
|||||||
@@ -49,8 +49,10 @@
|
|||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"domain_strategy": "prefer_ipv4",
|
"domain_strategy": "prefer_ipv4",
|
||||||
"endpoint_independent_nat": true,
|
"endpoint_independent_nat": true,
|
||||||
"inet4_address": "172.19.0.1/30",
|
"address": [
|
||||||
"inet6_address": "2001:0470:f9da:fdfa::1/64",
|
"172.19.0.1/30",
|
||||||
|
"2001:0470:f9da:fdfa::1/64"
|
||||||
|
],
|
||||||
"mtu": 9000,
|
"mtu": 9000,
|
||||||
"sniff": true,
|
"sniff": true,
|
||||||
"sniff_override_destination": true,
|
"sniff_override_destination": true,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ if ! command -v git &> /dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
git config --global --add safe.directory $(pwd)
|
git config --global --add safe.directory $(pwd)
|
||||||
git fetch --all && git reset --hard origin/dev && git pull origin dev
|
git fetch --all && git reset --hard origin/legacy && git pull origin legacy
|
||||||
rm -rf composer.lock composer.phar
|
rm -rf composer.lock composer.phar
|
||||||
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar
|
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar
|
||||||
php composer.phar update -vvv
|
php composer.phar update -vvv
|
||||||
|
|||||||
Reference in New Issue
Block a user