refactor: all-in-one docker deployment with auto-tuned resources and per-mode compose templates
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
# Xboard protocol fusion entrypoint.
|
||||||
|
#
|
||||||
|
# Caddy listens on a single public port and dispatches HTTP traffic to Octane
|
||||||
|
# while transparently upgrading WebSocket requests to the ws-server worker.
|
||||||
|
# This lets every external reverse proxy (nginx, Cloudflare, the user's own
|
||||||
|
# Caddy, ...) treat the panel as a single upstream and avoids exposing the
|
||||||
|
# 8076 WebSocket port directly.
|
||||||
|
{
|
||||||
|
admin off
|
||||||
|
auto_https off
|
||||||
|
persist_config off
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:{$CADDY_LISTEN_PORT:7001} {
|
||||||
|
@ws path /ws
|
||||||
|
reverse_proxy @ws 127.0.0.1:{$WS_PORT:8076}
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:{$OCTANE_INTERNAL_PORT:7002} {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Caddy config used by compose.split.yaml — the embedded image's Caddy is
|
||||||
|
# disabled in this mode and a dedicated Caddy container fronts independent
|
||||||
|
# web and ws-server containers reachable via the compose network.
|
||||||
|
{
|
||||||
|
admin off
|
||||||
|
auto_https off
|
||||||
|
persist_config off
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:7001 {
|
||||||
|
@ws path /ws
|
||||||
|
reverse_proxy @ws ws-server:8076
|
||||||
|
|
||||||
|
reverse_proxy web:7001 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
+122
-5
@@ -1,11 +1,128 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "[entrypoint] Running database migrations..."
|
# Resolve the binding scheme based on whether the embedded Caddy is enabled.
|
||||||
php /www/artisan migrate --force
|
#
|
||||||
|
# When ENABLE_CADDY=true (default), Caddy owns the public port (7001) and
|
||||||
|
# dispatches traffic internally; Octane and ws-server bind to localhost only
|
||||||
|
# so they cannot be reached from outside the container.
|
||||||
|
#
|
||||||
|
# When ENABLE_CADDY=false (e.g. an external reverse proxy or split mode),
|
||||||
|
# Octane takes the public port directly to keep behaviour identical to the
|
||||||
|
# pre-Caddy releases.
|
||||||
|
if [ "${ENABLE_CADDY}" = "true" ]; then
|
||||||
|
: "${OCTANE_HOST:=127.0.0.1}"
|
||||||
|
: "${OCTANE_PORT:=7002}"
|
||||||
|
: "${WS_HOST:=127.0.0.1}"
|
||||||
|
: "${WS_PORT:=8076}"
|
||||||
|
: "${CADDY_LISTEN_PORT:=7001}"
|
||||||
|
else
|
||||||
|
: "${OCTANE_HOST:=0.0.0.0}"
|
||||||
|
: "${OCTANE_PORT:=7001}"
|
||||||
|
: "${WS_HOST:=0.0.0.0}"
|
||||||
|
: "${WS_PORT:=8076}"
|
||||||
|
fi
|
||||||
|
export OCTANE_HOST OCTANE_PORT WS_HOST WS_PORT CADDY_LISTEN_PORT
|
||||||
|
export OCTANE_INTERNAL_PORT="${OCTANE_PORT}"
|
||||||
|
|
||||||
echo "[entrypoint] Checking core plugins..."
|
# ---------------------------------------------------------------------------
|
||||||
php /www/artisan tinker --execute="App\Services\Plugin\PluginManager::installDefaultPlugins();" 2>/dev/null || true
|
# Auto-tune worker counts based on the host (CPU + memory).
|
||||||
|
#
|
||||||
|
# Heuristic: each PHP worker (Octane/Horizon) costs ~80 MiB. After reserving
|
||||||
|
# ~300 MiB for the always-on processes (caddy/redis/ws-server/masters), divide
|
||||||
|
# the remaining budget across roles. Any user-set ENV wins.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
detect_cpus() {
|
||||||
|
if [ -r /sys/fs/cgroup/cpu.max ]; then
|
||||||
|
# cgroup v2: "<quota> <period>" or "max <period>"
|
||||||
|
read -r q p < /sys/fs/cgroup/cpu.max 2>/dev/null
|
||||||
|
if [ "$q" != "max" ] && [ -n "$q" ] && [ -n "$p" ] && [ "$p" -gt 0 ]; then
|
||||||
|
echo $(( (q + p - 1) / p ))
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
nproc 2>/dev/null || echo 1
|
||||||
|
}
|
||||||
|
|
||||||
echo "[entrypoint] Starting services..."
|
detect_mem_mib() {
|
||||||
|
if [ -r /sys/fs/cgroup/memory.max ]; then
|
||||||
|
m=$(cat /sys/fs/cgroup/memory.max 2>/dev/null)
|
||||||
|
if [ "$m" != "max" ] && [ -n "$m" ]; then
|
||||||
|
echo $(( m / 1024 / 1024 ))
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# No cgroup limit: avoid over-provisioning on big hosts. Cap the assumed
|
||||||
|
# budget to MEM_FALLBACK_MIB (default 1024) unless the user opts out by
|
||||||
|
# setting it explicitly. Use whichever is smaller of MemAvailable and cap.
|
||||||
|
avail=$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 1024)
|
||||||
|
cap=${MEM_FALLBACK_MIB:-1024}
|
||||||
|
[ "$avail" -lt "$cap" ] && echo "$avail" || echo "$cap"
|
||||||
|
}
|
||||||
|
|
||||||
|
CPUS=$(detect_cpus)
|
||||||
|
MEM_MIB=$(detect_mem_mib)
|
||||||
|
|
||||||
|
# Resource profile presets. RESOURCE_PROFILE selects ratios for the budget split:
|
||||||
|
# minimal - smallest possible footprint (~250-350 MiB), single octane worker,
|
||||||
|
# horizon capped to 1/1/1. Suitable for VPS with <=512 MiB RAM.
|
||||||
|
# balanced - default; ~80 MiB per worker, octane gets 25% of slots.
|
||||||
|
# performance - larger reserves for opcache/caches, more aggressive horizon caps.
|
||||||
|
# auto - same as balanced.
|
||||||
|
: "${RESOURCE_PROFILE:=auto}"
|
||||||
|
case "$RESOURCE_PROFILE" in
|
||||||
|
minimal) RESERVED_MIB=200; SLOT_MIB=100; OCT_NUM=1; OCT_DEN=1; OCT_FORCE=1; auto_horizon_mem=128; auto_octane_gc=64 ;;
|
||||||
|
performance) RESERVED_MIB=400; SLOT_MIB=70; OCT_NUM=1; OCT_DEN=3; OCT_FORCE=0; auto_horizon_mem=384; auto_octane_gc=256 ;;
|
||||||
|
balanced|auto|*) RESERVED_MIB=300; SLOT_MIB=80; OCT_NUM=1; OCT_DEN=4; OCT_FORCE=0; auto_horizon_mem=256; auto_octane_gc=128 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
BUDGET=$(( MEM_MIB - RESERVED_MIB ))
|
||||||
|
[ "$BUDGET" -lt "$SLOT_MIB" ] && BUDGET=$SLOT_MIB
|
||||||
|
SLOTS=$(( BUDGET / SLOT_MIB ))
|
||||||
|
|
||||||
|
clamp() { v=$1; lo=$2; hi=$3; [ "$v" -lt "$lo" ] && v=$lo; [ "$v" -gt "$hi" ] && v=$hi; echo "$v"; }
|
||||||
|
|
||||||
|
if [ "$OCT_FORCE" = "1" ]; then
|
||||||
|
auto_octane=1
|
||||||
|
auto_dp=1; auto_biz=1; auto_notif=1
|
||||||
|
else
|
||||||
|
auto_octane=$(clamp $(( (SLOTS * OCT_NUM) / OCT_DEN )) 1 "$CPUS")
|
||||||
|
remaining=$(( SLOTS - auto_octane - 2 ))
|
||||||
|
[ "$remaining" -lt 3 ] && remaining=3
|
||||||
|
auto_dp=$(clamp $(( remaining / 2 )) 1 $(( CPUS * 2 )))
|
||||||
|
auto_biz=$(clamp $(( remaining / 4 )) 1 "$CPUS")
|
||||||
|
auto_notif=$(clamp $(( remaining / 4 )) 1 "$CPUS")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# User-set ENV always wins.
|
||||||
|
: "${OCTANE_WORKERS:=$auto_octane}"
|
||||||
|
: "${OCTANE_TASK_WORKERS:=1}"
|
||||||
|
: "${OCTANE_MAX_REQUESTS:=500}"
|
||||||
|
: "${OCTANE_GARBAGE_MB:=$auto_octane_gc}"
|
||||||
|
: "${OCTANE_MAX_EXECUTION_TIME:=60}"
|
||||||
|
: "${HORIZON_DATA_PIPELINE_MAX:=$auto_dp}"
|
||||||
|
: "${HORIZON_BUSINESS_MAX:=$auto_biz}"
|
||||||
|
: "${HORIZON_NOTIFICATION_MAX:=$auto_notif}"
|
||||||
|
: "${HORIZON_WORKER_MEMORY_MB:=$auto_horizon_mem}"
|
||||||
|
: "${HORIZON_WORKER_MAX_TIME:=0}"
|
||||||
|
: "${HORIZON_WORKER_MAX_JOBS:=0}"
|
||||||
|
|
||||||
|
export OCTANE_WORKERS OCTANE_TASK_WORKERS OCTANE_MAX_REQUESTS \
|
||||||
|
OCTANE_GARBAGE_MB OCTANE_MAX_EXECUTION_TIME \
|
||||||
|
HORIZON_DATA_PIPELINE_MAX HORIZON_BUSINESS_MAX HORIZON_NOTIFICATION_MAX \
|
||||||
|
HORIZON_WORKER_MEMORY_MB HORIZON_WORKER_MAX_TIME HORIZON_WORKER_MAX_JOBS \
|
||||||
|
RESOURCE_PROFILE
|
||||||
|
|
||||||
|
echo "[entrypoint] Auto-tune (profile=${RESOURCE_PROFILE}): cpus=${CPUS} mem=${MEM_MIB}MiB slots=${SLOTS} -> octane=${OCTANE_WORKERS} horizon(dp/biz/notif)=${HORIZON_DATA_PIPELINE_MAX}/${HORIZON_BUSINESS_MAX}/${HORIZON_NOTIFICATION_MAX} horizon_worker_mem=${HORIZON_WORKER_MEMORY_MB}MB"
|
||||||
|
echo "[entrypoint] Horizon supervisors use balance=auto with minProcesses=1, so they scale up to the cap on demand and back down when idle."
|
||||||
|
|
||||||
|
if [ ! -s /www/.env ] || ! grep -qE '^INSTALLED=(1|true)$' /www/.env || echo " $* " | grep -q ' xboard:install '; then
|
||||||
|
echo "[entrypoint] Skipping xboard:update (not yet installed or running xboard:install)."
|
||||||
|
else
|
||||||
|
echo "[entrypoint] Running xboard:update..."
|
||||||
|
php /www/artisan xboard:update --no-interaction || \
|
||||||
|
echo "[entrypoint] WARNING: xboard:update failed; continuing so supervisor can boot anyway." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] Starting services (caddy=${ENABLE_CADDY} web=${ENABLE_WEB} horizon=${ENABLE_HORIZON} ws=${ENABLE_WS_SERVER})..."
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
; Slim PHP defaults for the all-in-one container.
|
||||||
|
; Tunables are overridable via Docker ENV (PHP_MEMORY_LIMIT, etc.) if needed.
|
||||||
|
|
||||||
|
memory_limit = 256M
|
||||||
|
|
||||||
|
[opcache]
|
||||||
|
opcache.enable = 1
|
||||||
|
opcache.enable_cli = 0
|
||||||
|
opcache.memory_consumption = 96
|
||||||
|
opcache.interned_strings_buffer = 16
|
||||||
|
opcache.max_accelerated_files = 10000
|
||||||
|
opcache.validate_timestamps = 0
|
||||||
|
opcache.revalidate_freq = 0
|
||||||
|
opcache.fast_shutdown = 1
|
||||||
|
|
||||||
|
[swoole]
|
||||||
|
swoole.use_shortname = Off
|
||||||
@@ -8,7 +8,7 @@ loglevel=info
|
|||||||
|
|
||||||
[program:octane]
|
[program:octane]
|
||||||
process_name=%(program_name)s_%(process_num)02d
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
command=php /www/artisan octane:start --host=0.0.0.0 --port=7001
|
command=php /www/artisan octane:start --host=%(ENV_OCTANE_HOST)s --port=%(ENV_OCTANE_PORT)s --workers=%(ENV_OCTANE_WORKERS)s --task-workers=%(ENV_OCTANE_TASK_WORKERS)s --max-requests=%(ENV_OCTANE_MAX_REQUESTS)s
|
||||||
autostart=%(ENV_ENABLE_WEB)s
|
autostart=%(ENV_ENABLE_WEB)s
|
||||||
autorestart=true
|
autorestart=true
|
||||||
user=www
|
user=www
|
||||||
@@ -65,7 +65,7 @@ priority=300
|
|||||||
|
|
||||||
[program:ws-server]
|
[program:ws-server]
|
||||||
process_name=%(program_name)s_%(process_num)02d
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
command=php /www/artisan ws-server start
|
command=php /www/artisan ws-server start --host=%(ENV_WS_HOST)s --port=%(ENV_WS_PORT)s
|
||||||
autostart=%(ENV_ENABLE_WS_SERVER)s
|
autostart=%(ENV_ENABLE_WS_SERVER)s
|
||||||
autorestart=true
|
autorestart=true
|
||||||
user=www
|
user=www
|
||||||
@@ -78,4 +78,21 @@ stopwaitsecs=5
|
|||||||
stopsignal=SIGINT
|
stopsignal=SIGINT
|
||||||
stopasgroup=true
|
stopasgroup=true
|
||||||
killasgroup=true
|
killasgroup=true
|
||||||
priority=400
|
priority=400
|
||||||
|
|
||||||
|
[program:caddy]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||||
|
autostart=%(ENV_ENABLE_CADDY)s
|
||||||
|
autorestart=true
|
||||||
|
user=root
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=5
|
||||||
|
stopsignal=TERM
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=500
|
||||||
@@ -29,4 +29,6 @@ frontend
|
|||||||
docker-compose.yaml
|
docker-compose.yaml
|
||||||
bun.lockb
|
bun.lockb
|
||||||
compose.yaml
|
compose.yaml
|
||||||
|
compose.host.yaml
|
||||||
|
compose.split.yaml
|
||||||
.scribe
|
.scribe
|
||||||
+10
-7
@@ -7,7 +7,7 @@ RUN CFLAGS="-O0" install-php-extensions pcntl && \
|
|||||||
CFLAGS="-O0 -g0" install-php-extensions bcmath && \
|
CFLAGS="-O0 -g0" install-php-extensions bcmath && \
|
||||||
install-php-extensions zip && \
|
install-php-extensions zip && \
|
||||||
install-php-extensions redis && \
|
install-php-extensions redis && \
|
||||||
apk --no-cache add shadow sqlite mysql-client mysql-dev mariadb-connector-c git patch supervisor redis && \
|
apk --no-cache add shadow sqlite mysql-client mysql-dev mariadb-connector-c git patch supervisor redis caddy && \
|
||||||
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 group redis || addgroup -S redis) && \
|
||||||
(getent passwd redis || adduser -S -G redis -H -h /data redis)
|
(getent passwd redis || adduser -S -G redis -H -h /data redis)
|
||||||
@@ -17,9 +17,9 @@ WORKDIR /www
|
|||||||
COPY .docker /
|
COPY .docker /
|
||||||
|
|
||||||
# Add build arguments
|
# Add build arguments
|
||||||
ARG CACHEBUST
|
ARG CACHEBUST=1
|
||||||
ARG REPO_URL
|
ARG REPO_URL=https://github.com/cedar2025/Xboard
|
||||||
ARG BRANCH_NAME
|
ARG BRANCH_NAME=master
|
||||||
|
|
||||||
RUN echo "Attempting to clone branch: ${BRANCH_NAME} from ${REPO_URL} with CACHEBUST: ${CACHEBUST}" && \
|
RUN echo "Attempting to clone branch: ${BRANCH_NAME} from ${REPO_URL} with CACHEBUST: ${CACHEBUST}" && \
|
||||||
rm -rf ./* && \
|
rm -rf ./* && \
|
||||||
@@ -29,8 +29,10 @@ RUN echo "Attempting to clone branch: ${BRANCH_NAME} from ${REPO_URL} with CACHE
|
|||||||
git submodule update --init --recursive --force
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
COPY .docker/caddy/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
COPY .docker/php/zz-xboard.ini /usr/local/etc/php/conf.d/zz-xboard.ini
|
||||||
|
|
||||||
RUN composer install --no-cache --no-dev \
|
RUN composer install --no-cache --no-dev --no-security-blocking \
|
||||||
&& 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 \
|
||||||
@@ -39,8 +41,9 @@ RUN composer install --no-cache --no-dev \
|
|||||||
|
|
||||||
ENV ENABLE_WEB=true \
|
ENV ENABLE_WEB=true \
|
||||||
ENABLE_HORIZON=true \
|
ENABLE_HORIZON=true \
|
||||||
ENABLE_REDIS=false \
|
ENABLE_REDIS=true \
|
||||||
ENABLE_WS_SERVER=false
|
ENABLE_WS_SERVER=true \
|
||||||
|
ENABLE_CADDY=true
|
||||||
|
|
||||||
EXPOSE 7001
|
EXPOSE 7001
|
||||||
COPY .docker/entrypoint.sh /entrypoint.sh
|
COPY .docker/entrypoint.sh /entrypoint.sh
|
||||||
|
|||||||
@@ -47,14 +47,15 @@ class XboardUpdate extends Command
|
|||||||
$this->info('正在检查并安装默认插件...');
|
$this->info('正在检查并安装默认插件...');
|
||||||
PluginManager::installDefaultPlugins();
|
PluginManager::installDefaultPlugins();
|
||||||
$this->info('默认插件检查完成');
|
$this->info('默认插件检查完成');
|
||||||
// Artisan::call('reset:traffic', ['--fix-null' => true]);
|
|
||||||
$this->info('正在重新计算所有用户的重置时间...');
|
|
||||||
Artisan::call('reset:traffic', ['--force' => true]);
|
|
||||||
$updateService = new UpdateService();
|
$updateService = new UpdateService();
|
||||||
$updateService->updateVersionCache();
|
$updateService->updateVersionCache();
|
||||||
$themeService = app(ThemeService::class);
|
$themeService = app(ThemeService::class);
|
||||||
$themeService->refreshCurrentTheme();
|
$themeService->refreshCurrentTheme();
|
||||||
Artisan::call('horizon:terminate');
|
try {
|
||||||
|
Artisan::call('horizon:terminate');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->warn('horizon:terminate skipped: ' . $e->getMessage());
|
||||||
|
}
|
||||||
$this->info('更新完毕,队列服务已重启,你无需进行任何操作。');
|
$this->info('更新完毕,队列服务已重启,你无需进行任何操作。');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ namespace App\Http\Controllers\V2\Server;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Services\ServerService;
|
use App\Services\ServerService;
|
||||||
|
use App\WebSocket\NodeWorker;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class ServerController extends Controller
|
class ServerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -16,14 +18,14 @@ class ServerController extends Controller
|
|||||||
{
|
{
|
||||||
$websocket = ['enabled' => false];
|
$websocket = ['enabled' => false];
|
||||||
|
|
||||||
if ((bool) admin_setting('server_ws_enable', 1)) {
|
if ((bool) admin_setting('server_ws_enable', 1) && Cache::has(NodeWorker::HEARTBEAT_CACHE_KEY)) {
|
||||||
$customUrl = trim((string) admin_setting('server_ws_url', ''));
|
$customUrl = trim((string) admin_setting('server_ws_url', ''));
|
||||||
|
|
||||||
if ($customUrl !== '') {
|
if ($customUrl !== '') {
|
||||||
$wsUrl = rtrim($customUrl, '/');
|
$wsUrl = rtrim($customUrl, '/');
|
||||||
} else {
|
} else {
|
||||||
$wsScheme = $request->isSecure() ? 'wss' : 'ws';
|
$wsScheme = $request->isSecure() ? 'wss' : 'ws';
|
||||||
$wsUrl = "{$wsScheme}://{$request->getHost()}:8076";
|
$wsUrl = "{$wsScheme}://{$request->getHttpHost()}/ws";
|
||||||
}
|
}
|
||||||
|
|
||||||
$websocket = [
|
$websocket = [
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ class NodeWorker
|
|||||||
private const AUTH_TIMEOUT = 10;
|
private const AUTH_TIMEOUT = 10;
|
||||||
private const PING_INTERVAL = 55;
|
private const PING_INTERVAL = 55;
|
||||||
|
|
||||||
|
public const HEARTBEAT_CACHE_KEY = 'ws_server:heartbeat';
|
||||||
|
private const HEARTBEAT_INTERVAL = 10;
|
||||||
|
private const HEARTBEAT_TTL = 30;
|
||||||
|
|
||||||
private Worker $worker;
|
private Worker $worker;
|
||||||
|
|
||||||
private array $handlers = [
|
private array $handlers = [
|
||||||
@@ -70,6 +74,11 @@ class NodeWorker
|
|||||||
|
|
||||||
private function setupTimers(): void
|
private function setupTimers(): void
|
||||||
{
|
{
|
||||||
|
Cache::put(self::HEARTBEAT_CACHE_KEY, time(), self::HEARTBEAT_TTL);
|
||||||
|
Timer::add(self::HEARTBEAT_INTERVAL, function () {
|
||||||
|
Cache::put(self::HEARTBEAT_CACHE_KEY, time(), self::HEARTBEAT_TTL);
|
||||||
|
});
|
||||||
|
|
||||||
Timer::add(self::PING_INTERVAL, function () {
|
Timer::add(self::PING_INTERVAL, function () {
|
||||||
$seen = [];
|
$seen = [];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Deployment for 1Panel users.
|
||||||
|
#
|
||||||
|
# 1Panel runs MySQL/Redis as separate containers on a Docker network named
|
||||||
|
# `1panel-network`. To let Xboard reach them by their container hostname
|
||||||
|
# (e.g. `1Panel-mysql-xxxx`), this compose file joins that external network
|
||||||
|
# in addition to publishing port 7001 for 1Panel's reverse proxy / website.
|
||||||
|
#
|
||||||
|
# During `php artisan xboard:install`, set:
|
||||||
|
# - Database Host: the container name shown in 1Panel under
|
||||||
|
# Database -> Connection Info -> Host
|
||||||
|
# - Redis: choose the built-in Redis (already provided by this image)
|
||||||
|
services:
|
||||||
|
xboard:
|
||||||
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "7001:7001"
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- 1panel-network
|
||||||
|
volumes:
|
||||||
|
- ./.env:/www/.env
|
||||||
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
|
- ./storage/logs:/www/storage/logs
|
||||||
|
- ./storage/theme:/www/storage/theme
|
||||||
|
- ./plugins:/www/plugins
|
||||||
|
- redis-data:/data
|
||||||
|
environment:
|
||||||
|
- RESOURCE_PROFILE=balanced # minimal | balanced | performance | auto
|
||||||
|
- ENABLE_HORIZON=true
|
||||||
|
- docker=true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
1panel-network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
xboard:
|
||||||
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
volumes:
|
||||||
|
- ./.env:/www/.env
|
||||||
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
|
- ./storage/logs:/www/storage/logs
|
||||||
|
- ./storage/theme:/www/storage/theme
|
||||||
|
- ./plugins:/www/plugins
|
||||||
|
- redis-data:/data
|
||||||
|
environment:
|
||||||
|
- RESOURCE_PROFILE=balanced # minimal | balanced | performance | auto
|
||||||
|
- ENABLE_HORIZON=true
|
||||||
|
- docker=true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
+23
-37
@@ -1,44 +1,30 @@
|
|||||||
|
# Default deployment: bridge network with port 7001 published to the host.
|
||||||
|
# Suitable for: bare docker-compose, aaPanel + Docker manager, custom reverse
|
||||||
|
# proxies (nginx, Caddy on host, Cloudflare Tunnel, etc.) that talk to
|
||||||
|
# 127.0.0.1:7001 on the host.
|
||||||
|
#
|
||||||
|
# For 1Panel users: use compose.1panel.sample.yaml so the container can reach
|
||||||
|
# the 1Panel-managed MySQL/Redis on the 1panel-network.
|
||||||
|
#
|
||||||
|
# For aaPanel native (openresty on host) users that prefer host networking:
|
||||||
|
# use compose.host.sample.yaml.
|
||||||
services:
|
services:
|
||||||
web:
|
xboard:
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./:/www/
|
|
||||||
environment:
|
|
||||||
- docker=true
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
network_mode: host
|
|
||||||
command: php artisan octane:start --port=7001 --host=0.0.0.0
|
|
||||||
restart: always
|
|
||||||
horizon:
|
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./:/www/
|
|
||||||
restart: always
|
|
||||||
network_mode: host
|
|
||||||
command: php artisan horizon
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
ws-server:
|
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./:/www/
|
|
||||||
restart: always
|
|
||||||
network_mode: host
|
|
||||||
command: php artisan ws-server start
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
command: redis-server --unixsocket /data/redis.sock --unixsocketperm 777
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "7001:7001"
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./.env:/www/.env
|
||||||
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
|
- ./storage/logs:/www/storage/logs
|
||||||
|
- ./storage/theme:/www/storage/theme
|
||||||
|
- ./plugins:/www/plugins
|
||||||
- redis-data:/data
|
- redis-data:/data
|
||||||
sysctls:
|
environment:
|
||||||
net.core.somaxconn: 1024
|
- RESOURCE_PROFILE=balanced # minimal | balanced | performance | auto
|
||||||
|
- ENABLE_HORIZON=true
|
||||||
|
- docker=true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis-data:
|
redis-data:
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Split deployment for K8s users or operators who want to scale, restart and
|
||||||
|
# limit each process independently. The single image is reused across services
|
||||||
|
# by overriding the command and disabling the supervisor programs that are not
|
||||||
|
# relevant to that role.
|
||||||
|
#
|
||||||
|
# Topology:
|
||||||
|
# caddy ──┬─→ web (HTTP) :7001 → octane :7001
|
||||||
|
# └─→ ws-server (WebSocket) /api/v2/server/ws → :8076
|
||||||
|
# horizon (queue worker, no public port)
|
||||||
|
# redis (state)
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "7001:7001"
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
- ws-server
|
||||||
|
volumes:
|
||||||
|
- ./.docker/caddy/Caddyfile.split:/etc/caddy/Caddyfile:ro
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
volumes: &shared-volumes
|
||||||
|
- ./.env:/www/.env
|
||||||
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
|
- ./storage/logs:/www/storage/logs
|
||||||
|
- ./storage/theme:/www/storage/theme
|
||||||
|
- ./plugins:/www/plugins
|
||||||
|
environment:
|
||||||
|
docker: "true"
|
||||||
|
ENABLE_CADDY: "false"
|
||||||
|
ENABLE_HORIZON: "false"
|
||||||
|
ENABLE_WS_SERVER: "false"
|
||||||
|
|
||||||
|
horizon:
|
||||||
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
volumes: *shared-volumes
|
||||||
|
environment:
|
||||||
|
docker: "true"
|
||||||
|
ENABLE_CADDY: "false"
|
||||||
|
ENABLE_WEB: "false"
|
||||||
|
ENABLE_WS_SERVER: "false"
|
||||||
|
|
||||||
|
ws-server:
|
||||||
|
image: ghcr.io/cedar2025/xboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
volumes: *shared-volumes
|
||||||
|
environment:
|
||||||
|
docker: "true"
|
||||||
|
ENABLE_CADDY: "false"
|
||||||
|
ENABLE_WEB: "false"
|
||||||
|
ENABLE_HORIZON: "false"
|
||||||
|
WS_HOST: "0.0.0.0"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --unixsocket /data/redis.sock --unixsocketperm 777 --save 900 1 --save 300 10 --save 60 10000
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
+12
-3
@@ -176,7 +176,10 @@ return [
|
|||||||
'balance' => 'auto',
|
'balance' => 'auto',
|
||||||
'autoScalingStrategy' => 'time',
|
'autoScalingStrategy' => 'time',
|
||||||
'minProcesses' => 1,
|
'minProcesses' => 1,
|
||||||
'maxProcesses' => 8,
|
'maxProcesses' => (int) env('HORIZON_DATA_PIPELINE_MAX', 8),
|
||||||
|
'memory' => (int) env('HORIZON_WORKER_MEMORY_MB', 128),
|
||||||
|
'maxTime' => (int) env('HORIZON_WORKER_MAX_TIME', 3600),
|
||||||
|
'maxJobs' => (int) env('HORIZON_WORKER_MAX_JOBS', 1000),
|
||||||
'balanceCooldown' => 1,
|
'balanceCooldown' => 1,
|
||||||
'tries' => 3,
|
'tries' => 3,
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
@@ -186,7 +189,10 @@ return [
|
|||||||
'queue' => ['default', 'order_handle'],
|
'queue' => ['default', 'order_handle'],
|
||||||
'balance' => 'simple',
|
'balance' => 'simple',
|
||||||
'minProcesses' => 1,
|
'minProcesses' => 1,
|
||||||
'maxProcesses' => 3,
|
'maxProcesses' => (int) env('HORIZON_BUSINESS_MAX', 3),
|
||||||
|
'memory' => (int) env('HORIZON_WORKER_MEMORY_MB', 128),
|
||||||
|
'maxTime' => (int) env('HORIZON_WORKER_MAX_TIME', 3600),
|
||||||
|
'maxJobs' => (int) env('HORIZON_WORKER_MAX_JOBS', 1000),
|
||||||
'tries' => 3,
|
'tries' => 3,
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
],
|
],
|
||||||
@@ -196,7 +202,10 @@ return [
|
|||||||
'balance' => 'auto',
|
'balance' => 'auto',
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
'minProcesses' => 1,
|
'minProcesses' => 1,
|
||||||
'maxProcesses' => 3,
|
'maxProcesses' => (int) env('HORIZON_NOTIFICATION_MAX', 3),
|
||||||
|
'memory' => (int) env('HORIZON_WORKER_MEMORY_MB', 128),
|
||||||
|
'maxTime' => (int) env('HORIZON_WORKER_MAX_TIME', 3600),
|
||||||
|
'maxJobs' => (int) env('HORIZON_WORKER_MAX_JOBS', 1000),
|
||||||
'tries' => 3,
|
'tries' => 3,
|
||||||
'timeout' => 60,
|
'timeout' => 60,
|
||||||
'backoff' => [3, 10, 30],
|
'backoff' => [3, 10, 30],
|
||||||
|
|||||||
+8
-10
@@ -102,8 +102,8 @@ return [
|
|||||||
|
|
||||||
OperationTerminated::class => [
|
OperationTerminated::class => [
|
||||||
FlushTemporaryContainerInstances::class,
|
FlushTemporaryContainerInstances::class,
|
||||||
DisconnectFromDatabases::class,
|
// DisconnectFromDatabases::class,
|
||||||
CollectGarbage::class,
|
// CollectGarbage::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
WorkerErrorOccurred::class => [
|
WorkerErrorOccurred::class => [
|
||||||
@@ -129,6 +129,7 @@ return [
|
|||||||
|
|
||||||
'warm' => [
|
'warm' => [
|
||||||
...Octane::defaultServicesToWarm(),
|
...Octane::defaultServicesToWarm(),
|
||||||
|
\App\Services\Plugin\PluginManager::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'flush' => [
|
'flush' => [
|
||||||
@@ -147,8 +148,8 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'cache' => [
|
'cache' => [
|
||||||
'rows' => 5000,
|
'rows' => (int) env('OCTANE_CACHE_ROWS', 1000),
|
||||||
'bytes' => 20000,
|
'bytes' => (int) env('OCTANE_CACHE_BYTES', 8192),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -163,10 +164,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'tables' => [
|
'tables' => [
|
||||||
'example:1000' => [
|
//
|
||||||
'name' => 'string:1000',
|
|
||||||
'votes' => 'int',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -203,7 +201,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'garbage' => 128,
|
'garbage' => (int) env('OCTANE_GARBAGE_MB', 128),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -216,6 +214,6 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'max_execution_time' => 60,
|
'max_execution_time' => (int) env('OCTANE_MAX_EXECUTION_TIME', 60),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-shot backfill of users.next_reset_at for legacy installs.
|
||||||
|
*
|
||||||
|
* Replaces the previous `reset:traffic --force` step in `xboard:update`,
|
||||||
|
* which had to run on every container start. Now it runs exactly once per
|
||||||
|
* database (Laravel migrations are tracked).
|
||||||
|
*/
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!Schema::hasColumn('v2_user', 'next_reset_at')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Artisan::call('reset:traffic', ['--fix-null' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Backfill is non-destructive; nothing to roll back.
|
||||||
|
}
|
||||||
|
};
|
||||||
+18
-103
@@ -33,33 +33,20 @@ sudo bash quick_start.sh
|
|||||||
|
|
||||||
2. Configure Reverse Proxy:
|
2. Configure Reverse Proxy:
|
||||||
```nginx
|
```nginx
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://127.0.0.1:8076;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_read_timeout 60s;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ / {
|
location ^~ / {
|
||||||
proxy_pass http://127.0.0.1:7001;
|
proxy_pass http://127.0.0.1:7001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Real-PORT $remote_port;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header Scheme $scheme;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header Server-Protocol $server_protocol;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Server-Name $server_name;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Server-Addr $server_addr;
|
proxy_set_header Connection $http_connection;
|
||||||
proxy_set_header Server-Port $server_port;
|
proxy_read_timeout 60s;
|
||||||
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
> The `/ws/` location enables WebSocket real-time node synchronization via `ws-server`. This service is enabled by default and can be toggled in Admin Panel > System Settings > Server.
|
> The all-in-one container's embedded Caddy fuses HTTP and the panel↔node WebSocket on port 7001. The single `Upgrade`/`Connection` pair above is enough; no separate `/ws/` location is needed. To opt out and expose Octane / `:8076` directly, set `ENABLE_CADDY=false` in `compose.yaml`.
|
||||||
|
|
||||||
3. Install Xboard:
|
3. Install Xboard:
|
||||||
```bash
|
```bash
|
||||||
@@ -74,85 +61,22 @@ yum update && yum install -y git
|
|||||||
|
|
||||||
# Clone repository
|
# Clone repository
|
||||||
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard ./
|
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard ./
|
||||||
|
# (Optional shortcut: skip the clone and just fetch the sample file with
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/cedar2025/Xboard/master/compose.sample.yaml -o compose.yaml
|
||||||
|
# — the running PHP code is in the Docker image, not in the clone.)
|
||||||
|
|
||||||
# Configure Docker Compose
|
# Configure Docker Compose
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Edit compose.yaml:
|
4. Prepare `compose.yaml` from the **1Panel-specific** sample. This sample joins the external `1panel-network` so the container can reach the 1Panel-managed MySQL/Redis containers by their hostname:
|
||||||
```yaml
|
```bash
|
||||||
services:
|
cp compose.1panel.sample.yaml compose.yaml
|
||||||
web:
|
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./.env:/www/.env
|
|
||||||
- ./.docker/.data/:/www/.docker/.data
|
|
||||||
- ./storage/logs:/www/storage/logs
|
|
||||||
- ./storage/theme:/www/storage/theme
|
|
||||||
- ./plugins:/www/plugins
|
|
||||||
environment:
|
|
||||||
- docker=true
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
command: php artisan octane:start --host=0.0.0.0 --port=7001
|
|
||||||
restart: on-failure
|
|
||||||
ports:
|
|
||||||
- 7001:7001
|
|
||||||
networks:
|
|
||||||
- 1panel-network
|
|
||||||
|
|
||||||
horizon:
|
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./.env:/www/.env
|
|
||||||
- ./.docker/.data/:/www/.docker/.data
|
|
||||||
- ./storage/logs:/www/storage/logs
|
|
||||||
- ./plugins:/www/plugins
|
|
||||||
restart: on-failure
|
|
||||||
command: php artisan horizon
|
|
||||||
networks:
|
|
||||||
- 1panel-network
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
ws-server:
|
|
||||||
image: ghcr.io/cedar2025/xboard:new
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
- ./.env:/www/.env
|
|
||||||
- ./.docker/.data/:/www/.docker/.data
|
|
||||||
- ./storage/logs:/www/storage/logs
|
|
||||||
- ./plugins:/www/plugins
|
|
||||||
restart: on-failure
|
|
||||||
ports:
|
|
||||||
- 8076:8076
|
|
||||||
networks:
|
|
||||||
- 1panel-network
|
|
||||||
command: php artisan ws-server start
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
command: redis-server --unixsocket /data/redis.sock --unixsocketperm 777
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- 1panel-network
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
redis-data:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
1panel-network:
|
|
||||||
external: true
|
|
||||||
```
|
```
|
||||||
|
The file is gitignored so your edits survive `git pull`. See [docker-compose.md](./docker-compose.md) for tuning environment variables (`RESOURCE_PROFILE`, `ENABLE_HORIZON`, `ENABLE_REDIS`, etc.) and the other `compose.*.sample.yaml` alternatives.
|
||||||
|
|
||||||
5. Initialize Installation:
|
5. Initialize Installation:
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies and initialize
|
docker compose run -it --rm xboard php artisan xboard:install
|
||||||
docker compose run -it --rm web php artisan xboard:install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ Important Configuration Notes:
|
⚠️ Important Configuration Notes:
|
||||||
@@ -186,20 +110,11 @@ docker compose up -d
|
|||||||
|
|
||||||
## 4. Version Update
|
## 4. Version Update
|
||||||
|
|
||||||
> 💡 Important Note: The update command varies depending on your installation version:
|
|
||||||
> - If you installed recently (new version), use this command:
|
|
||||||
```bash
|
```bash
|
||||||
docker compose pull && \
|
docker compose pull && docker compose up -d
|
||||||
docker compose run -it --rm web php artisan xboard:update && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
> - If you installed earlier (old version), replace `web` with `xboard`:
|
|
||||||
```bash
|
The container always runs `php artisan xboard:update` (migrate + plugin install + version cache + theme refresh) on boot, so no extra command is required.
|
||||||
docker compose pull && \
|
|
||||||
docker compose run -it --rm xboard php artisan xboard:update && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
> 🤔 Not sure which to use? Try the new version command first, if it fails, use the old version command.
|
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ cd /www/wwwroot/your-domain
|
|||||||
chattr -i .user.ini
|
chattr -i .user.ini
|
||||||
rm -rf .htaccess 404.html 502.html index.html .user.ini
|
rm -rf .htaccess 404.html 502.html index.html .user.ini
|
||||||
|
|
||||||
# Clone repository
|
# Clone the compose branch
|
||||||
git clone https://github.com/cedar2025/Xboard.git ./
|
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard.git ./
|
||||||
|
|
||||||
# Prepare configuration file
|
# Prepare configuration file
|
||||||
cp compose.sample.yaml compose.yaml
|
cp compose.sample.yaml compose.yaml
|
||||||
|
|
||||||
# Install dependencies and initialize
|
# Install dependencies and initialize
|
||||||
docker compose run -it --rm web sh init.sh
|
docker compose run -it --rm xboard php artisan xboard:install
|
||||||
```
|
```
|
||||||
> ⚠️ Please save the admin dashboard URL, username, and password shown after installation
|
> ⚠️ Please save the admin dashboard URL, username, and password shown after installation
|
||||||
|
|
||||||
@@ -84,54 +84,30 @@ docker compose up -d
|
|||||||
#### 3.4 Configure Reverse Proxy
|
#### 3.4 Configure Reverse Proxy
|
||||||
Add the following content to your site configuration:
|
Add the following content to your site configuration:
|
||||||
```nginx
|
```nginx
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://127.0.0.1:8076;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_read_timeout 60s;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ / {
|
location ^~ / {
|
||||||
proxy_pass http://127.0.0.1:7001;
|
proxy_pass http://127.0.0.1:7001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Real-PORT $remote_port;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header Scheme $scheme;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header Server-Protocol $server_protocol;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Server-Name $server_name;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Server-Addr $server_addr;
|
proxy_set_header Connection $http_connection;
|
||||||
proxy_set_header Server-Port $server_port;
|
proxy_read_timeout 60s;
|
||||||
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
> The `/ws/` location enables real-time node synchronization via `ws-server`. This service is enabled by default and can be toggled in Admin Panel > System Settings > Server.
|
> The all-in-one container's embedded Caddy fuses HTTP and the panel↔node WebSocket on port 7001. The single `Upgrade`/`Connection` pair above is enough; no separate `/ws/` location is needed. To opt out and expose Octane / `:8076` directly, set `ENABLE_CADDY=false` in `compose.yaml`.
|
||||||
|
|
||||||
## Maintenance Guide
|
## Maintenance Guide
|
||||||
|
|
||||||
### Version Updates
|
### Version Updates
|
||||||
|
|
||||||
> 💡 Important Note: Update commands may vary depending on your installed version:
|
|
||||||
> - For recent installations (new version), use:
|
|
||||||
```bash
|
```bash
|
||||||
docker compose pull && \
|
docker compose pull && docker compose up -d
|
||||||
docker compose run -it --rm web sh update.sh && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
> - For older installations, replace `web` with `xboard`:
|
|
||||||
```bash
|
The container always runs `php artisan xboard:update` (migrate + plugin install + version cache + theme refresh) on boot, so no extra command is required.
|
||||||
git config --global --add safe.directory $(pwd)
|
|
||||||
git fetch --all && git reset --hard origin/master && git pull origin master
|
|
||||||
docker compose pull && \
|
|
||||||
docker compose run -it --rm xboard sh update.sh && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
> 🤔 Not sure which to use? Try the new version command first, if it fails, use the old version command.
|
|
||||||
|
|
||||||
### Routine Maintenance
|
### Routine Maintenance
|
||||||
- Regular log checking: `docker compose logs`
|
- Regular log checking: `docker compose logs`
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ systemctl start docker
|
|||||||
|
|
||||||
### 2. Deployment Steps
|
### 2. Deployment Steps
|
||||||
|
|
||||||
1. Get project files:
|
1. Clone the `compose` branch (it ships `compose.sample.yaml` and the other `compose.*.sample.yaml` variants):
|
||||||
```bash
|
```bash
|
||||||
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard
|
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard
|
||||||
cd Xboard
|
cd Xboard
|
||||||
```
|
cp compose.sample.yaml compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
2. Install database:
|
2. Install database:
|
||||||
|
|
||||||
@@ -29,13 +30,23 @@ 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
|
xboard php artisan xboard:install
|
||||||
```
|
```
|
||||||
- Custom configuration installation (Advanced users)
|
- Custom configuration installation (Advanced users)
|
||||||
```bash
|
```bash
|
||||||
docker compose run -it --rm web php artisan xboard:install
|
docker compose run -it --rm xboard php artisan xboard:install
|
||||||
```
|
```
|
||||||
> Please save the admin dashboard URL, username, and password shown after installation
|
> Please save the admin dashboard URL, username, and password shown after installation
|
||||||
|
> The repository ships **four** compose templates in the `compose` branch — pick the one matching your setup, copy it to `compose.yaml`, then run the install command:
|
||||||
|
>
|
||||||
|
> | File | Network | When to use |
|
||||||
|
> |------|---------|-------------|
|
||||||
|
> | `compose.sample.yaml` | bridge + ports `7001:7001` | bare docker, custom reverse proxy, aaPanel + Docker (default) |
|
||||||
|
> | `compose.host.sample.yaml` | `network_mode: host` | aaPanel native (openresty on host) |
|
||||||
|
> | `compose.1panel.sample.yaml` | bridge + external `1panel-network` | 1Panel users (so the container can reach 1Panel-managed MySQL/Redis) |
|
||||||
|
> | `compose.split.sample.yaml` | multi-container (web/horizon/ws-server/redis split) | K8s migration, advanced scaling |
|
||||||
|
>
|
||||||
|
> The local `compose.yaml` is gitignored so your edits survive `git pull` when you do clone the repo.
|
||||||
|
|
||||||
3. Start services:
|
3. Start services:
|
||||||
```bash
|
```bash
|
||||||
@@ -48,22 +59,10 @@ docker compose up -d
|
|||||||
|
|
||||||
### 3. Version Updates
|
### 3. Version Updates
|
||||||
|
|
||||||
> 💡 Important Note: Update commands may vary depending on your installed version:
|
|
||||||
> - For recent installations (new version), use:
|
|
||||||
```bash
|
```bash
|
||||||
cd Xboard
|
cd Xboard
|
||||||
docker compose pull && \
|
docker compose pull && docker compose up -d
|
||||||
docker compose run -it --rm web php artisan xboard:update && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
> - For older installations, replace `web` with `xboard`:
|
|
||||||
```bash
|
|
||||||
cd Xboard
|
|
||||||
docker compose pull && \
|
|
||||||
docker compose run -it --rm xboard php artisan xboard:update && \
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
> 🤔 Not sure which to use? Try the new version command first, if it fails, use the old version command.
|
|
||||||
|
|
||||||
### 4. Version Rollback
|
### 4. Version Rollback
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user