- PHP 8.4+, Hyperf 3.2, Swoole 6.2 resident/coroutine server;
bin/hyperf.phpis the CLI entrypoint andcore.phpbootstraps configuration. - PSR-4 maps
App\toapp/; controllers use Hyperf annotation routes.App\Controller\AbstractControllerprovides request parsing and response helpers. - HTTP listens on
0.0.0.0:9501; both deprecated/1/and current/v1/API routes are supported (a/{version:v?1}route prefix matches both). The/ragMCP endpoint is served by the same process and accepts loopback requests only unlessai.mcp.rag.authTokenis configured. - Storage is selected by
storage.storageId: MariaDB (s) or filesystem (f). Redis is an optional cache/rate-limit dependency. OpenLiteWaf/andOpenLiteStats/are independent GitHub repos mounted as git submodules — they have their own READMEs, tests, and release lifecycle.
bin/hyperf.php 入口文件(Hyperf Application)
core.php 引导文件(定义 CORE_PATH 并加载 Config)
app/ 核心类库(App\ 命名空间)
├── Agent/ LogAgent(模型驱动工具循环)与排障工具链
├── Cache/ Redis 缓存实现与协程连接池
├── Client/ AI、MCP、GitHub、Redis、SpinYarn 客户端
├── Command/ Hyperf 命令(rag:build 等)
├── Controller/ HTTP 控制器(注解路由,含 Log、AI、Admin 控制台等)
├── Data/ 数据模型(Token、MetadataEntry)
├── Filter/ 预处理过滤链(敏感信息脱敏)
├── Middleware/ CORS、AdminAuth、限流中间件
├── Parser/ 请求体解码(Brotli / Gzip / Deflate 自动解压与炸弹防护)
├── Process/ 常驻消费者进程(AiQueueConsumer、EventQueueConsumer)
├── Queue/ 统一日志异步事件队列(EventQueue、QueueEvent、DeadLetterQueue、Handler/)
├── Rag/ 本地 RAG 检索引擎(SQLite FTS5 + 语义向量召回)
├── Response/ 统一响应结构封装(ApiResponse)
├── Sse/ SSE 流式输出封装(SseWriter、AnalysisEmitter)
├── Storage/ 持久化存储后端(MariaDbStorage、FilesystemStorage)
├── System/ 系统核心服务(SecurityService、AuditLogManager、TelemetryService、SpinYarnManager、LogPartitionManager 分区回收)
├── ApiError.php API 业务异常(ApiExceptionHandler 统一渲染)
├── Config.php 配置加载器、环境变量覆盖与动态热重载引擎
├── ContentParser.php 请求正文与启动器特征识别
├── Detective.php 日志类型与服务端版本探测
├── UploadParser.php 多文件安全校验与 ZIP 展开引擎
├── Log.php 日志核心模型与反混淆持久化
└── Id.php ID 生成与存储后端编解码
config/autoload/ Hyperf 框架配置(server、databases、middlewares 等)
rag/ 内置 RAG:knowledge/ 静态知识库,index.db 索引(构建生成,勿提交)
docker/ Compose 编排、nginx 站点配置、镜像构建
OpenLiteWaf/ 边缘 WAF 独立子模块(OpenResty Lua,CC 防御与攻击特征拦截)
OpenLiteStats/ 边缘站点访问统计独立子模块(无锁位图 UV、流量统计与日志分析)
Config.inc.php 全部配置(gitignored)
Config.inc.example.php 配置模板
.env.example 环境变量模板
AGENTS.md 项目核心架构约定与上下文文档
API.md 完整 API 文档
CHANGELOG.md 更新日志
docs/ 历史过程文档与设计规划
git submodule update --init --recursive # OpenLiteWaf / OpenLiteStats 以 submodule 引入,clone 或 pull 部署后必须执行
composer install
cp Config.inc.example.php Config.inc.php
# Termux (Android) 验证命令:因缺少 /usr/bin/env,直接执行 composer test / composer stan 会报错 127 (pest/phpstan: not found),必须以 php 调用;若智能体命令沙箱报错 invalid argument,需以 unsandboxed / BypassSandbox 运行:
php vendor/bin/pest
php vendor/bin/pest --group=architecture
PHPSTAN_TURBO=0 php vendor/bin/phpstan analyse app --level=5 --memory-limit=512M
# CI / Ubuntu 环境标准命令:
composer test
composer test:architecture
composer stan
# 启动与索引:
php bin/hyperf.php list
php bin/hyperf.php rag:build
php bin/hyperf.php start- Tests use Pest and bootstrap through
tests/bootstrap.php; that bootstrap createsConfig.inc.phpwhen absent and supplies a Redis mock when ext-redis is unavailable. Run one file withphp vendor/bin/pest tests/Unit/FilterTest.phpor filter by name withphp vendor/bin/pest --filter=...; architecture tests are thearchitecturePest group (php vendor/bin/pest --group=architecture/composer test:architecture).tests/Unit/ControllersUnitTest.phpcovers HTTP controllers via direct reflection instantiation to ensure line coverage is captured by PCOV without Hyperf AOP proxy interference. - Integration tests need MariaDB and Redis (missing MariaDB locally will skip
MariaDbStorageTestwithout failing). CI initializes MariaDB withdocker/mariadb-init.sqlfollowed byscripts/sync_mariadb_events.phpanddocker/mariadb-events.sqlso thatcleanup_expired_logsevent checks pass. CI runners includebrotliextension to fully test compressed requests.RequestParser::decodeBodyandContentParserdecompress brotli via single-argumentbrotli_uncompress($body)and validate against decompressed size limits in application code (ext-brotli second argument is dictionary?string, passing integer throws TypeError).RedisClient::setTestConnection(?object $conn)enables unit tests (such asAnalysisQueueTest) to run deterministically againstRedisMockregardless of whetherext-redisis loaded, eliminating unnecessary skips. Local Docker services are started withdocker compose -f docker/compose.yaml up -d. CI (.github/workflows/ci.yaml) runs comprehensive live environment integration tests viascripts/ci_e2e_test.phpagainst both the booted resident Swoole server (port 9501) and the production Docker stack with Nginx SSL, OpenLiteWaf attack blocking, OpenLiteStats analytics, complete log lifecycle (create/read/raw/files/meta/insights/telemetry/delete/404), and Admin/RAG MCP endpoints. - Lua regression tests (Termux):
php OpenLiteWaf/tests/openlitewaf_regex_test.php,lua5.1 OpenLiteWaf/tests/openlitewaf_logic_test.lua,lua5.1 OpenLiteStats/tests/openlitestats_logic_test.lua,luac5.1 -p OpenLiteWaf/lua/*.lua OpenLiteStats/lua/*.lua. The two Lua test suites stub ngx and use real file IO under/data/data/com.termux/files/usr/tmp/for persistence cases. - PHPStan analyzes
app/at level 5. The existingignoreErrorsentries inphpstan.neonare intentional (Codex type hierarchy inapp/Log.php, Hyperf ResponsegetConnection()inapp/Sse/SseWriter.php, whole-filemethod.unusedinapp/Agent/LogAgent.phpbecause its remaining private thin delegates are a reflection-compatibility shell for 40+ legacy reflection tests), andapp/Client/SpinYarnClient.phpis excluded because it depends on the spinyarn PHP extension that is absent in the CI static-analysis environment; do not add suppressions or exclusions casually. - There is no configured formatter. Before finishing code changes, run the relevant Pest tests, architecture tests, and
PHPSTAN_TURBO=0 php vendor/bin/phpstan analyse app --level=5 --memory-limit=512Mon Termux.
- Copy
Config.inc.example.phpto the gitignoredConfig.inc.php; never commit it or expose its API keys. Database and Redis connection settings can be overridden withDB_*andREDIS_*; AI settings in.envincludeAI_ENABLED,AI_API_KEYS,AI_BASE_URL,AI_MODEL, and JSONAI_RAG_PROVIDERS. - Do not change
id.charactersor the ID length: existing log IDs depend on them. IDs are seven characters, withs/fidentifying the storage backend. - Upload limits are enforced before storage: 10 MB / 50,000 lines, plus at most 200 files and 12 MB total. ZIP uploads must remain protected against traversal and excessive expansion.
- SpinYarn is optional and only parses mappings already present under
mappings/; it has no automatic download. The extension and mapping files are primarily handled by the Docker build/bind mount.spinyarn_init()gained a 5thredis_urlparameter in SpinYarn v1.1.0 (pinned to v1.1.1 since v1.7.7; v1.1.1 brings bincode mapping cache and resident connection); v1.0.0 accepted only 4 parameters —SpinYarnClient::supportsRedisArg()detects the loaded signature via reflection and adapts, because blindly passing 5 args to v1.0.0 throws ArgumentCountError and the fail-open path then disables deobfuscation for the whole process (2026-09 production incident). Since v1.1.0 the local LRU cache is gone (Redis-backed only; without aredis_urlmappings are parsed on demand). Restart the server after swapping the extension .so. - RAG knowledge base: hand-maintained diagnostic assets under
rag/knowledge/(日志分析 / patterns / format / android-native-lib / mobile_launcher); site legal/announcement copy lives indocs/site/and crash-report test fixtures intests/Fixtures/rag_tools/, both outside the index. The machine-fetch scripts and theUPSTREAM_DIRSwhitelist were retired with the move to the GitHub live troubleshooting toolchain. New knowledge directories must be registered inRagSearch::TOPIC_DESCRIPTIONS(qualitative descriptions only — no drifting counts), enforced by the bidirectionaltopic descriptions cover all knowledge directoriesunit test. The retrieval chunk unit is the##second-level heading, so title-less bulk files (version tables) must be hand-segmented (seerag/README.md). Recall quality is gated by the gold set:php scripts/rag_eval.php(paraphrase tier is the signal; rerun after any knowledge-content or ranking change — a paraphrase hit@5 regression below thebasevariant exits non-zero; not in CI because it needs online embedding; in-CI ranking regressions are locked bytests/Unit/RagFusionTest.phppure-computation cases). Enabling semantic RAG requires re-runningphp bin/hyperf.php rag:build(builds into a temp SQLite DB, then atomically replacesrag/index.db; failures keep the old index). - Swoole is a resident process: process-level caches and extension handles survive requests. Restart the server after changing parsing/deobfuscation behavior or other process-level state. The hyperf image runs every process under jemalloc (
LD_PRELOAD=...libjemalloc.so.2+MALLOC_CONFfast decay): glibc malloc never returns a resident process's peak RSS after connection-sized frees fragment the heap top, which made the queue consumer plateau at 1.4GB on the 3.6GB host; jemalloc's decay-based purge keeps RSS near live size. Keep both ENVs when editing the Dockerfile. - The Docker MariaDB init script runs only for a new database volume. MariaDB runs Event Scheduler and
mariadb-eventsapplies the generatedcleanup_expired_logsSQL;scripts/sync_mariadb_events.phpreadsConfig.inc.phpas the sole TTL source, including on existing volumes. MariaDB healthcheck verifies the event exists and is enabled. Production Compose reads secrets from the gitignored.env;MARIADB_PASSWORD,MARIADB_ROOT_PASSWORD, andREDIS_PASSWORDmust be supplied consistently to Hyperf, MariaDB, Redis, and MariaDB Event services. Non-secret application settings remain inConfig.inc.php. AI is disabled by default and configuration validation requiresAI_ENABLED=true,AI_API_KEYS,AI_BASE_URL, andAI_MODEL; semantic RAG additionally requires valid JSONAI_RAG_PROVIDERSentries with embedding configuration; vector recall is primary and lexical results supplement it. - Admin API (
/{version:v?1}/admin/*): gated byadmin.enabledand protected byApp\Middleware\AdminAuthMiddleware(timing-safe check againstadmin.token, overridable viaADMIN_ENABLEDandADMIN_TOKEN). Docker deployment mounts hostruntime/to/app/runtimeand hostOpenLiteWaf/data/to/app/OpenLiteWaf/data:ro(docker/compose.yaml) to persist runtime state and allow WAF snapshot telemetry. Endpoints includeGET /logs(paginated querying viaStorageInterface::List/Count, keyword/source/time filters),GET /logs/{id}(full detail without token),DELETE /logs/{id}(forced admin deletion without deletion token),POST /logs/batch-delete(criteria-based batch deletion with limit safety),GET /security/bans(active banned IPs from Redis & OpenLiteWaf),POST /security/ban&POST /security/unban(manual IP ban/unban with dual-layer Redis/JSON persistence; unban additionally calls the edgePOST /security/unbanon nginx whenOPENLITEWAF_ADMIN_TOKENis configured, otherwise the WAF-side ban is left to its TTL),GET /security/overview(WAF attack metrics & categories, with multi-path snapshot probing, schema normalization, and internal cURL fallback),GET/PUT /security/content-rules(hot content rejection keywords & regex rules persisted toruntime/content_reject_rules.jsonand dual-written todynamic_config.json),GET /analytics/sources(client/launcher source distribution),GET /analytics/versions(Minecraft version and mod loader matrix; primary source islog_metadatakeysversion/loaderauto-derived server-side at upload time byLog::deriveEcosystemMetadata(), with log header & mod list heuristic recognition as fallback only when metadata is absent),GET /analytics/trends(log volume & size time-series trends over whole natural days starting at the earliest day's local midnight, with average log size estimated from an in-window 300-row sample to eliminate table-scan timeouts),GET /system/storage-health(physical database table sizes, filesystem disk space and Redis memory diagnostics),POST /system/cleanup-expired(manual trigger for expired log cleanup),POST /system/cache/flush(pattern-based cache eviction),GET /system/queue(AI queue metrics),GET /ai/metrics(AI throughput, duration percentiles, token estimates & topic hits;ragCallsandtopicsare counted at the RAG service landing pointRagControllerrag_search branch —topicsderives from the directory of each returned hit, NOT from the model-suppliedtopicargument, which is usually absent and would keep the chart empty),GET /ai/queue/inspect(deep queue inspection, active workers, pending jobs & dead letters),POST /ai/queue/pause&POST /ai/queue/resume(manual consumer pause/resume control),POST /ai/queue/flush(safe queue backlog clearing),POST /ai/queue/dead/clear(dead jobs purge),GET/POST /ai/domain-knowledge&PUT/DELETE /ai/domain-knowledge/{id}(已知领域知识条目管理,单条严格限制 ≤200 字并直接拼接入系统提示词,LLM 自身无写权限亦不走 Tools,作为强制先验规则),GET /ai/analyses(分析记录分页检索,支持模式/评分/时间多维过滤与统计)、GET /ai/analyses/{cacheKey}(单条分析完整 trace + score + validation + toolCallChain)、GET /ai/analyses/{cacheKey}/trace(导出单条 trace JSON)、DELETE /ai/analyses/{cacheKey}(安全删除分析记录与缓存)、GET /ai/scoreboard(四维质量评分概览与趋势看板)、GET /ai/scoreboard/slow(慢分析耗时排行与调优排查)、GET /ai/scoreboard/low-score(低分告警分析列表)、GET/POST /ai/prompts(Prompt 版本清单 / 从当前版本 Fork 派生新版本)、GET/PUT/DELETE /ai/prompts/{promptVersion}(获取完整提示词 / 更新提示词片段 / 删除版本)、POST /ai/prompts/{promptVersion}/activate(激活指定 Prompt 版本毫秒级跨进程热生效)、GET /ai/tools(已注册 9 大排障工具状态清单)、PUT /ai/tools/{name}/enable(在线启用/禁用排障工具门控)、PUT /ai/tools/{name}/config(工具重试与 Fallback 链配置)、GET /ai/modes&PUT /ai/modes/{mode}(deep / launcher / quick 排障模式参数可视化配置),GET /system/stats(system and runtime stats),GET/PUT /config(masked inspection and dynamic hot-reload configuration persisting toruntime/dynamic_config.json;App\Config::ensureFresh()detectsfilemtime,filesize, and microsecond string version in Redis across Swoole resident worker processes to hot-reload in milliseconds without restarting workers;Config::load()incorporatesConfig.inc.example.phpas baseline to ensure legacy configs expose all modern sections in admin UI),POST /config/reset,POST /config/test-ai(connectivity probing with custom headers support) /POST /config/test-rag-provider(connectivity probing),GET /rag/stats(knowledge base chunks/embeddings and topic map),POST /rag/build&GET /rag/build/status(async rebuild persisting status toruntime/rag_build_status.json),GET/PUT /rag/config(检索链路能力开关 chunker/rerank/queryRewrite/incrementalBuild/semanticCache/telemetry,白名单式逐字段局部合并后跨进程热生效;rerank 支持type=llm(复用主分析模型 listwise)与type=http(专用 cross-encoder 端点,POST {baseUrl}/rerank,Cohere/Jina/SiliconFlow/vLLM/Xinference 约定,响应兼容results[].relevance_score与裸数组[].score),baseUrl拒绝私网地址、回环需显式allowLoopback,apiKey读取只回显掩码并由Config::restoreMaskedSecrets()回填),POST /rag/test-rerank(以两条固定文档探测精排端点,失败仍返回 200 且success=false),GET /rag/build/stale(按文件 mtime 比对知识库与索引差异,索引未构建返回 409) &POST /rag/build/incremental(仅重索引 changed/missing 文件),GET /rag/telemetry(按自然日的分阶段检索耗时与命中聚合,summary 为 Redis hash 原样透出、未启用阶段的键直接缺席,消费方须区分「未采集」与「计数为零」;?slow=1附慢查询明细),POST /rag/search(diagnostic recall), RAG document CMS endpointsGET /rag/topics,GET /rag/docs(topic/keyword filters),GET /rag/docs/content,POST /rag/docs/save(create/update),DELETE /rag/docs(safe deletion),POST /rag/docs/upload(multipart/JSON upload, 5MB limit, strict path traversal and extension whitelisting), SpinYarn mapping and probe endpointsGET /spinyarn/status&POST /spinyarn/test(mapping library inventory and online deobfuscation test), high-risk operation audit loggingGET/DELETE /audit/logs(persisted via Redis ring buffer andruntime/logs/audit.logwith action, keyword, and time range filtering), telemetry endpointsGET/DELETE /telemetry/stats(client-side metrics dashboard and aggregation reset; public report endpoint atPOST /{version}/telemetry/reportwith raw body fallback parsing for beacon/non-JSON clients), and system diagnosticsGET/DELETE /system/logs(paginated querying via Redis ring buffersyslog:recentfalling back toruntime/logs/system.log, level/keyword filters, and safe clear).ai.headerssupports custom HTTP headers (also overridable viaAI_HEADERSenv) for upstream gateway compatibility (e.g. OpenRouter, Cloudflare), with credential masking/unmasking. - LogAgent 深度排障与架构重构机制(
app/Agent/):- 核心架构拆解与解耦:原
LogAgent彻底重构拆解为单一职责体系,涵盖AgentRuntime(多轮 tool loop 引擎,分层停止条件)、ToolRegistry/ToolFactory(统一工具注册、调用与门控)、PromptBuilder/PromptManager(提示词版本化与动态拼接)、LogWindowManager(动态聚焦崩溃上下文行)、ResultValidator(结构化结论验证与未核实声明标注)、AnalysisScorer(工具效率/证据充分性/结论明确性四维评分)、AnalysisTracer(完整执行链路 Trace 记录与 JSON 导出)、AnalysisRecordManager(基于 Redis ZSET 与本地归档的分析记录持久化存储)。 - 三种分析模式(AnalysisMode):支持
deep(深度排障,默认 50 轮上限与全工具链)、launcher(启动器崩溃优先,20 轮上限与 GitHub/RAG 优先)、quick(极速单轮直答)。 - 结构化输出与前台渲染:诊断正文末尾附带严格校验的结构化 JSON 输出块(根因、置信度、排障清单、证据追踪),前台客户端自动解析并渲染为核心卡片。
- 深度因果与线索查找硬性约束:重构系统提示词与思维链(CoT)策略,明确复杂日志绝非仅看表象错误。除非报错原因极其明确孤立无需上下文(如纯物理内存不足或单点孤立 Java 主版本不匹配),否则模型【必须至少进行一次日志上下文线索查找】(使用
read_log_file读取报错前后上下文行区间、检索crash-reports崩溃报告附件、或调用grep_log_file追踪前置异常链与模组初始化状态),探明真正诱因;严禁仅凭初始截取的单点错误切片草率适可而止或放弃工具。 - 管理员已知领域知识强行拼接:由运维团队在后台增删改查已知业务规则与排障先验知识(落盘
runtime/domain_knowledge.json并同步 Redis 缓存,单条 ≤200 字),在组织分析提示词时直接拼接入系统提示词段落『已知领域知识』,LLM 自身无写权限且不走动态 Tools 读取,形成权威业务约束。
- 核心架构拆解与解耦:原
- Client IP resolution and application security: Public rate limiting and DDoS defense are handled by Nginx/OpenLiteWaf at the edge. Application-layer real client IP resolution is centralized in
SecurityService::resolveClientIp()using strict trusted-proxy verification (security.trustedProxies/rateLimit.trustedProxies/ RFC 1918 private subnets and loopback addresses); untrusted public origins cannot spoofX-Real-IPorX-Forwarded-For. All public write/analysis endpoints (LogController,AnalyseController,AIAnalyseController) enforcecheckIpBan()(supporting exact IP and CIDR subnets against Config, Redis, local JSON and OpenLiteWaf snapshot). 违规关键词与正则表达式采用 AES-256-GCM 对称加密存储(密文标识enc:v1:<base64(iv.tag.cipher)>),密钥由环境变量SECURITY_ENCRYPTION_KEY、配置或自动生成存盘于runtime/.security_secret(0600 权限保护),runtime/content_reject_rules.json及dynamic_config.json磁盘落盘严格全量密文存储,杜绝敏感词泄露;Config::validate()自动解密后再进行正则表达式语法校验,Admin 控制台则解密后友好呈现。 - 统一日志异步事件队列(EventQueue,
app/Queue):- 极速上传解耦:用户上传日志(
LogController::create)彻底移除同步阻塞的 SpinYarn 反混淆和全量文本关键词正则审核,仅保留快速preFilter()并执行$this->analyse()轻量 Codex 分析(探测版本与元数据),数据落库(Storage::Put)后立即派发EventQueue::EVENT_LOG_UPLOADED事件并毫秒级向客户端响应 200 JSON 成功结果。 - 事件模型与发布订阅注册中心:引入强类型事件模型
QueueEvent(封装事件名称、UUID、尝试次数attempts、最大重试maxAttempts、时间戳与stopPropagation()阻断控制);EventQueue::register(eventName, handler, priority)支持按优先级降序调度自定义监听器,所有处理器实现EventHandlerInterface(标准解耦,支持实例、类名与闭包注入),支持动态挂载新任务(如索引、通知、Webhook 等)。 - 调度中心与降级保障:
EventQueue::dispatch()优先将事件序列化并投递至 Redis Stream(键名events:log:stream,消费组log-event-workers,近似裁剪上限 10000 条);若 Redis 未配置或不可用,在 Swoole 协程环境下自动派发子协程异步执行,在 CLI/单测环境下同步降级执行,保证 100% 健壮可用;内置实时指标监控EventQueue::getStats()与吞吐计数。 - 异常重试策略与死信队列(DeadLetterQueue / DLQ):消费者执行异常时若重试次数未耗尽将保留待决由退避协程重试;达到
maxAttempts上限后自动将事件、原始载荷、失败堆栈转存入死信流events:log:dead并安全出队,杜绝毒丸永久阻塞;Admin 控制台支持查看死信(GET /v1/admin/event-queue/dead)、重试重放(POST /v1/admin/event-queue/dead/retry)、清空(DELETE /v1/admin/event-queue/dead)以及队列运行概览(GET /v1/admin/event-queue/stats)。 - 队列指标口径:消费成功后
XACK+XDEL,因此streamLength/backlog稳态恒为 0,面板不得据此判定队列空转;累计吞吐以消费组entries-read(getStats()输出为entriesRead,需 Redis 7.0+)与counters计数为准,consumers给出当前组内消费者数。counters分processed_success/processed_blocked/processed_failed三口径:异步安全审核命中违规并阻断流转(日志已删除、来源已封禁)计入processed_blocked,属预期治理结果,不再污染processed_failed;getStats()对 Redis 检视失败的静默catch已改为\App\Syslog::error()记录。 - 异步安全审计与反混淆流水线:
SecurityAuditHandler提取主日志正文与附加文件(Log::getRawFiles())执行规则检测,若违规即刻物理删除日志及缓存($log->delete())、阻断后续流转(stopPropagation)、记录security.async_reject高危操作审计日志,并自动封禁非私网恶意来源 IP;DeobfuscateHandler对未混淆/支持的日志调用 SpinYarn 并通过统一的StorageInterface::Update()与Log::updateContent()完成反混淆持久化与 Redis 缓存原子回写。 - 常驻事件消费者进程:
EventQueueConsumer(继承AbstractProcess,注解#[Process(name: 'event-queue-consumer')],由isEnable()受控启动),在进程内派生固定命名消费协程(worker-0,worker-1)拉取流事件,按 Pipeline 调度执行,成功或安全阻断后立即执行XACK与XDEL杜绝流积压;后台协程周期性(30s)执行XAUTOCLAIM回收异常崩溃超时的待决条目;支持两阶段排空(30s 宽限期)与三重自回收策略(累计处理 ≥1000 任务、OS 真实物理常驻内存 VmRSS ≥256MB 或空闲 ≥180s),退出后由 Swoole manager 自动重新拉起干净进程重置 RSS。
- 极速上传解耦:用户上传日志(
- AI analysis micro-queue (
ai.queue, default off): when enabled, ALL/v1/ai/*analyses run in theai-queue-consumercustom process (registered via the#[Process]annotation onApp\Process\AiQueueConsumer, gated byisEnable(); Hyperf 3.xprocesses.phpexpects process instances, not 2.x array configs) and the HTTP handler only relays the job's Redis Stream frames.maxConcurrentconsumer coroutines bound upstream quota;maxQueueis the queue-depth cap — depth is real backlog (XINFO GROUPSlag + pending for theai-analysersgroup, NOTXLENwhich counts cumulative entries and would cause permanent 429 after enough traffic; before the group exists it falls back to XLEN), full returns 429 +Retry-Afterbefore SSE begins;waitTimeoutbounds the relay wait and0/negative means no timeout (wait until done/error or client disconnect, in which casejobTtlalone is the job lifetime viaAnalysisQueue::jobLifetime()); a client disconnect does not cancel the job (result still cached);failOpen(default true) falls back to inline execution when Redis is unavailable. Jobs carry the log content in a short-TTL gzip payload key (never in the Stream itself) and are deduped per cache key viaai:job:active:*; crashed consumers are recovered by XAUTOCLAIM (claimIdleMs) with a per-job running lock preventing double execution. Stream has MAXLEN cap (max(1000, maxQueue * 4)) onxAddand completed/dropped entries are physically removed viaxDel, preventing unbounded Stream growth. Consumers use fixed names (worker-0,worker-1) without PID suffixes to prevent stale consumer pileup (historical 191 stale consumers incident), andcleanStaleConsumersautomatically purges orphaned consumers with 0 pending. Reclaim loop enforces dead letter defense (max 3 deliveries per entry before dropping + ACK + XDEL), and missing-payload terminations deduplicate terminal error frames. Consumer memory management: per-jobgc_collect_cycles()andgc_mem_caches()curtail zend_alloc heap fragmentation; self-recycle runs on a triple-condition (processed ≥500 jobs regardless of idle as hard cap, or OS resident VmRSS ≥256MB reading/proc/self/statusavoiding Zend MM metric delusion, or idle ≥180s with processed ≥50; enters two-phase graceful drain stopping new Redis stream reads, waits for active jobs to complete with a 30s deadline, and exits for Swoole manager restart to reset RSS — solving the concurrent job overlapping deadlock and memory leak incident). Emission goes throughApp\Sse\AnalysisEmitter(SseEmitter= inline,StreamEmitter= queue; both produce byte-identical SSE frames) — the active emitter lives in Hyperf context, never a plain static. Enabling the queue requires ext-redis +cache.redisand a server restart; PHP changes to the consumer require rebuilding the hyperf image. - Docker build versions are pinned in
docker/hyperf.Dockerfile; the standard Compose deployment exposes Nginx on ports 80/443, its config isdocker/nginx/default.conf, TLS certificates are mounted read-only from the gitignoreddocker/certs/directory, and ACME HTTP-01 challenges usedocker/acme/. - Edge WAF:
OpenLiteWaf/is a standalone OpenResty-Lua project (submodule) running inside the nginx container (imageopenresty/openresty:1.27.1.2-alpine, not stock nginx).- Entry points are
access_by_lua_file/log_by_lua_file/content_by_lua_fileindocker/nginx/default.conf; http-level lua directives live in the mountedOpenLiteWaf/nginx/nginx.conf(lua_shared_dict openlitewaf+openlitestats,lua_package_path,init_by_lua_block,init_worker_by_lua_block). - Public stats page:
/security(HTML),/security/stats(JSON summary) and/security/logs(JSON, attack logs paginated 50/page, ring buffer of 500). Public log entries mask IPs and blanktoken=params; "banned IP count" is an approximation via ring slots (shared dict cannot enumerate keys). Each log entry records which rule fired and on which object (rule+via= uri/path/ua/body) — never the matched snippet, so request bodies cannot leak through the public page./security/statsadditionally exposesban_reasons(keys likeprobe#57/cc,?for bans restored from a pre-1.3.0 snapshot). - Rule matching is scoped per rule via an optional third table element (
uri/path/ua/body, space-separated; omitted = all objects). Path-shaped probe signatures (.php,/.git/,/cgi-bin/, sensitive-file extensions) MUST stayuri/path: filenames such asmain.log,config.ymlorphpmyadminappearing in a POST body or User-Agent are just text (shared logs, knowledge-base docs, frontend telemetry) and matching them used to ban legitimate clients' IPs for 600s. Injection/XSS/traversal/RCE rules keep scanning the body. An unparseable scope token degrades that rule to "all objects" and logs to the nginx error log rather than silently disabling it. - Signatures match raw request_uri, fully decoded request_uri (incl. query), normalized uri, User-Agent, and request body (POST/PUT/PATCH; requests with Content-Length >2MB are skipped, chunked bodies are always scanned; first 64KB only).
body_exempt_prefixesskips body scanning entirely for endpoints that ingest arbitrary user text:/v1|/1/log,/v1|/1/ai/analyse,/v1|/1/analyse,/v1|/1/telemetry(the Web-UI SDK reports every fetched URL inendpoint),/v1|/1/admin(knowledge-base Markdown and prompt text). The exemption covers the body only — URI and UA are still matched. Raw file endpoints/{version}/raw/{id}/{filename}bypass the extension rule viais_raw_file_request. Categories: sqli/xss/traversal/rce/probe. - CC defense returns 429 with
Retry-Afterand does NOT write the ban key: the per-window counter key keeps throttling within the window and self-clears in the next one, and CC is not evidence of an attack, so a CGNAT egress must not be blackholed. Signature hits return 403 and increment a per-IP strike counter (s:<ip>, windowsig_strike_window); the IP is only banned aftersig_strikes(default 3) hits, which caps the blast radius of one false positive at a single 403 instead of 10 minutes of site-wide 403s.blocked_totalcounts every refusal while category counters only count judged hits — their difference is ban-window repeats, and that gap widening is the signal of a new false positive. - The CC threshold must stay below the nginx
limit_reqrate (30r/s inlocation /), or requests get 503-dropped by limit_req before OpenLiteWaf ever sees them; that linkage only holds for reverse-proxied traffic, since thelocation =entries served by Lua (/security,/stats) have nolimit_reqand are governed by CC alone. - Runtime unbanning goes through the privileged Lua endpoints
POST /security/unban?ip=andGET /security/bans(443 server only), authenticated by theX-OpenLiteWaf-Tokenheader against theOPENLITEWAF_ADMIN_TOKENenv var, which must be declared with a main-contextenvdirective inOpenLiteWaf/nginx/nginx.confforos.getenvto see it. Unset/short token ⇒ 404 (fail-closed); comparison is non-short-circuiting. The handler deletesb:<ip>, the strike counter, the CC window keys AND thebs:/br:/bx:slots, then rewrites the snapshot immediately — clearing only the ban key lets the 60s snapshot re-persist it andinit_by_luaresurrects it on restart. Bans survivedocker restartfor the same reason. The PHP side (SecurityService::syncUnbanToWaf) calls it over internal HTTPS; it no longer editssnapshot.json, andOpenLiteWaf/datastays:roin the app container on purpose. - Data persists via a 60s snapshot written by worker 0 to the read-write mounts
/data/openlitewaf(hostOpenLiteWaf/data/, gitignored) and restored ininit_by_lua; if the dir is unwritable it degrades to in-memory mode. deny() setsngx.ctx.olw_blockedwhich OpenLiteStats uses to exclude blocked requests. - Rules and CC thresholds are in
OpenLiteWaf/lua/openlitewaf.lua; changes take effect only after the nginx Lua VM re-reads them —git pullalone does NOT apply, and in the current deploymentdocker exec logshare-nginx nginx -s reloadis UNRELIABLE (the container's nginx pid file points at a stale master PID, so the HUP never reaches the running master; verified 2026-08-30). Usedocker restart logshare-nginxinstead — snapshot persistence means counters/bans/logs now survive restarts (≤60s data loss window), so restarting is NOT a way to clear a wrong ban. OpenLiteWaf/tests/openlitewaf_regex_test.phpandOpenLiteWaf/tests/openlitewaf_logic_test.luaare the regression tests for rules and logic. The PHP suite parsesRULES-BEGIN/END, fails when a declared rule is silently unparsed, validates scope tokens, and takes a per-samplekind(uri/ua/body); the Lua suite stubsngx.reso it can only assert flow/counting/scoping, never regex correctness — real ngx.re + body coverage lives inscripts/ci_e2e_test.php --waf-test.
- Entry points are
- Site analytics:
OpenLiteStats/is a second standalone submodule (https://github.com/NingZeStudio/OpenLiteStats) in the same nginx container.- It records PASSED requests in the log phase (
log_by_lua_file .../lstats/log.lua; bytes_sent is only available there): requests, outbound bytes, unique IPs (64K bitmap + linear counting, daily reset), hourly buckets, plus top endpoints/referers (host only, stripping basic auth credentials)/UAs and a 20-entry recent list aggregated at view time from a 1000-entry ring buffer; IPs are masked (IPv4 two octets, IPv6 first 3 hextets expanding::abbreviation totop3::*), URI has no query. - Pages:
/stats(HTML) and/stats/data(JSON).CONFIG.exclude_prefixes=/security,/stats,/v1|/1/telemetry,/v1|/1/adminare excluded from ALL counters (the check runs before counting, the UV bitmap and the ring buffer), matching the fact that telemetry is machine traffic from half of all requests and admin polling is operational traffic. Exclusion is path-segment bounded (/v1/administratoris counted,/v1/admin/logsis not) andstarts_withrejects an empty prefix, since an empty entry would silently zero the whole site's stats. CORS preflightOPTIONSis skipped inrecord(). - Top-endpoint keys are templated at aggregation time only (
/v1/raw/{id}/main.log→/v1/raw/:id/main.log, ≥16-hex cacheKeys →:hash) using the same ID definition asTelemetryService::cleanEndpoint(1 storage char + 6 alnum chars, and the segment itself must contain a digit so 7-letter routes likepreview/backupsare not folded); therecentlist keeps raw paths for per-instance debugging. - Persistence mirrors the WAF: 60s snapshot from worker 0 to the
OpenLiteStats/data/mount (/data/openlitestats), restored ininit_by_lua. Itslua_shared_dict openlitestats 32mlives inOpenLiteWaf/nginx/nginx.conf. - Regression test:
lua5.1 OpenLiteStats/tests/openlitestats_logic_test.lua.
- It records PASSED requests in the log phase (
- 根因:InnoDB 的
DELETE只会在.ibd文件内部留下空洞并等待复用,永不把空间归还操作系统,磁盘占用单调递增直至爆满。实测删除 171MB 数据后磁盘占用纹丝不动(200MB → 200MB)。唯一能真正归还空间的手段是DROP PARTITION(直接删除分区的.ibd文件),而OPTIMIZE TABLE/FORCE需要约等量的临时磁盘空间,在磁盘将满时反而不可用。 - 结构:
logs/log_files/log_metadata三表均按created做天级 RANGE 分区(docker/mariadb-init.sql),另保留p_future(VALUES LESS THAN MAXVALUE)兜底分区承接未覆盖的行——缺少兜底分区会让新写入直接以Table has no partition for value失败。 - 分区表硬性约束(MariaDB,违反即 ERROR 1506):分区表不支持外键,原
ON DELETE CASCADE已全部移除,级联删除必须由MariaDbStorage显式完成(先子表后父表),漏删任一张表都会留下孤儿行;所有唯一键(含主键)必须包含分区键,故主键为(id, created)/(log_id, name, created)/(log_id, key, created)。 - 子表 created 必须与父表一致:分区回收按同一
created边界进行。Renew()已同步更新子表created,否则父子行落入不同分区,父表分区被丢弃后子表残留成为永久无法回收的孤儿数据(MariaDbStorageTest::testRenewKeepsChildTablesInSync守门)。 - 历史分区只在安装时铺一次:DDL 的分区边界必须是编译期常量,
scripts/sync_mariadb_events.php在生成 SQL 时把[今天-TTL, 今天]的边界固化成字面量并写入docker/mariadb-events.sql。运行期严禁重建历史窗口——过期分区会被正常丢弃,此时补建会与幸存的较新分区重名(ERROR 1517),形成「丢弃→重建→冲突」死循环。运行期不再做任何 REORGANIZE,只做 DROP;跨天延伸靠重跑生成脚本并重装事件(mariadb-events 容器重建即重装)。 - 分区名哨兵判据:判断历史区间是否已建过时,用「最靠近今天的那个历史分区」是否存在,不要用分区计数——过期分区被丢弃后计数必然少于期望值,会被误判为「未建过」而触发重名冲突。
- 有符号比较陷阱:
information_schema.PARTITIONS.PARTITION_DESCRIPTION是无符号列,与有符号cutoff比较会发生类型提升,负 cutoff 被提升为巨大无符号数,导致「没有任何分区可丢弃」且不报任何错误,清理静默失效。存储过程与 SQL 侧一律写CAST(PARTITION_DESCRIPTION AS SIGNED) <= v_cutoff;v_cutoff也必须声明为BIGINT SIGNED。 - DROP 必须逐个执行:同一会话内
information_schema.PARTITIONS在 DDL 之后可能返回陈旧视图,批量拼接的分区列表会混入已被丢弃的分区名,使整条ALTER语法失败(ERROR 1064)并作废该表本次清理。存储过程因此逐个分区「查一个丢一个」,并设 400 次上限防止陈旧视图导致的无界循环卡死 Event Scheduler。 - 两层清理路径:
MariaDbStorage::CleanupExpired()(Admin 手动触发)与存储过程logshare_cleanup_expired()(cleanup_expired_logs事件,每小时)共用同一套逻辑——直接DROP PARTITION、最后cleanupStrays()按行回收滞留在p_future的过期数据(兜底分区无法按子区间 DROP;这批数据通常极少,仅分区维护中断期间写入)。 - 分区只按整日回收:
DROP条件是分区上界 ≤ cutoff,因此跨越 cutoff 当天的分区会保留到次日。这是刻意的保守设计(分区可能仍含未过期数据),代价是空间回收有最多一天的滞后。 - 部署迁移:结构不兼容(主键变更 + 移除外键),旧库需清空重建:
php scripts/migrate_partitioned_storage.php --yes,随后php scripts/sync_mariadb_events.php并重启mariadb-events容器。脚本带--yes保护与幂等判据(已是分区结构且含fingerprint列则直接退出)。 - 改
storage.storageTime后必须重新执行scripts/sync_mariadb_events.php并重启mariadb-events容器,否则历史分区窗口与清理边界仍是旧 TTL。 - 去重指纹:
MariaDbStorage::fingerprint()用xxh128(非加密哈希,速度约为 SHA-256 的十倍量级,10MB 正文开销在毫秒级)计算「正文 + 附加文件(按名称排序,顺序无关)+ source」。Put()先按idx_fingerprint等值匹配(一次 B-tree 定位,与日志总量无关,故可安全放在上传关键路径),命中则直接返回既有 ID,不再写入新行。旧库缺列时静默降级为不去重,绝不阻断上传。 - 观测:
GET /v1/admin/system/storage-health现返回dataFreeBytes/fragmentationRatio/partitions(各表日分区数、覆盖边界、p_future占用、largestFreePartition空闲指向)。注意fragmentationRatio在小数据量下天然偏高(InnoDB extent 预分配,实测 86 行零删除即 0.39),以dataFreeBytes绝对值与趋势为准;空闲集中在活跃分区属健康,散落旧分区才是真空洞。
AnalysisRecordManager::list()默认把pageSize夹到 100(HTTP 侧的 DoS 防护,不得放宽或透传请求参数);内部聚合调用方(getScoreboard())必须显式传入放宽的$maxPageSize上限并按全量结果计算均值与计数,否则统计口径会静默退化为「只统计最新 100 条」——该缺陷曾让totalAnalyses恒为 100 且days参数失效,由AdminAiTest的回归用例守门。- Controllers must use injected PSR-7 requests,
ContentParser, storage abstractions, andAbstractControllerhelpers; do not read$_SERVER,$_GET, or$_POST, and do not issue raw SQL from controllers. Architecture tests enforce this. - FastRoute does NOT url-decode route parameters (verified:
dispatchreturnsai%3Aanalysis%3A…verbatim). Analysis cache keys are colon-bearing (ai:analysis:<logId>/ai:analysis:hash:<sha256>) and the admin console sends them percent-encoded, so any controller taking such a path parameter must decode it first —AdminController::decodePathKey()exists precisely because the analysis detail/trace/delete endpoints 404'd while the list worked. Unit-test new key-carrying routes with the encoded form. SecurityService::getWafOverview()is a whitelist projection of the WAF JSON: a new OpenLiteWaf field is invisible to the admin console until it is explicitly added there (this is howban_reasonswould have been lost). LikewiseunbanIp()returnswaf_sync(ok/skipped/failed/invalid) because an app-layer unban does not clear an edge ban — the console must not report a flat "已解封".- Controllers receiving JSON or structured input must parse requests via
$this->getParsedBody()provided byAbstractController: it prioritizes the framework's$request->getParsedBody()and automatically falls back to raw stream JSON deserialization if the upstream client sends non-JSON Content-Type (e.g.text/plainfrom defaultfetch). - Throw
App\ApiErrorfor expected API failures;ApiExceptionHandlerrenders the API error response. - Client IP resolution (
SecurityService::resolveClientIp): strictly adheres to configuredtrustedProxies(overriding implicit trust); traversesX-Forwarded-Forfrom right to left to peel off trusted proxies and extract the outermost untrusted IP, completely eliminating header spoofing;RateLimitMiddlewarepasses full request headers toresolveClientIpensuring proxy clients are not coalesced into loopback. - AES-256-GCM rules encryption: key generation enforces 0600 permissions at temporary creation with atomic rename and collision resolution; decryption failure throws RuntimeException instead of silently falling back to ciphertext, preventing malformed ciphertext from polluting runtime keyword filtering chains.
- EventQueue & DLQ: retry counts are tracked and persisted in Redis (
events:attempts:{$id}) with TTL protection; when retries exhaust,DeadLetterQueue::pushreturn is strictly verified beforexAckandxDel, preventing silent event drops. RedisClient: explicitly configuresOPT_READ_TIMEOUT(10s default) exceeding consumer blocking wait times (2s/5s) to eliminate periodic socket disconnection storm.- Container startup:
docker/hyperf.Dockerfileentrypoint explicitly wipes stale mounted annotation container cache (rm -rf /app/runtime/container;) before scanning and booting, preventing route 404 and proxy discrepancy. - Apply configured pre-filters before storage. Deletion tokens are stored hashed; plaintext tokens are returned only by the upload response.
- Use
App\Syslog::error()for diagnostics, not rawerror_log(). - SSE output must go through
App\Sse\AnalysisEmitter(SseEmitterinline /StreamEmitterqueued), which writes viaApp\Sse\SseWriter; request-scoped stream state (including the active emitter) belongs in Hyperf context rather than a plain static. - AI tool calls remain model-controlled; do not force a RAG call in
LogAgentwhen the model returns no tools. AI routes are 404 whenai.enabledis false. LogAgent初始分析日志上下文(首轮发给模型的日志):当日志总长度 < 12KB 时不触发定位算法,完整内容直传 user message;当日志总长度 ≥ 12KB 时统一触发错误特征定位正则(与前端 logParser 对齐的 Tier 1 根因/堆栈与 Tier 2 失败谓词体系),若定位到错误行则以此为核心截取 12KB 聚焦窗口(预留约 2.5KB 前置因果与完整后置堆栈,整行对齐)塞入 user message;若未定位到任何显式错误,绝不盲目截取开服前缀日志塞入 user message,而是向模型提供日志概况、提示基础常用的 grep 关键词(ERROR、FATAL、Exception、Caused by、crash 等),并引导模型遵循“适可而止”思维链开展 1~2 轮定向探测,若无异常即客观输出结论,避免无休止调用。LogAgent提供日志文件关键词检索工具grep_log_file(类似 grep),仅在绑定日志 ID 时开放;支持指定filename(默认main)、query、case_sensitive、context_lines(0–5,默认 1)和max_matches(1–30,默认 10);行号按总行数字符宽度左填充对齐,命中行标记>,上下文行标记空格,相邻或重叠行区间自动合并;单次工具输出放宽至 32KB(与 RAG 检索一致),状态摘要提取首行概况及命中行标记。LogAgent集成 GitHub 启动器/渲染器排障工具链:提供github_list_repos、github_search、github_get_content三大工具(受github.enabled开关控制),替代易过期的静态启动器与渲染器实战 issue 知识库;支持通过github.tokens或GITHUB_TOKENS配置多 Personal Access Token,内置轮询均衡、Rate-Limit 状态感知与 403/429 自动冷却故障转移(Failover);结合 Redis 缓存(搜索 1h、详情 24h)规避 GitHub 限流;提示词核心原则强调所有 Tools 包括知识库都仅为辅助日志分析手段,并非必须依赖或每轮必调,通读日志足以形成结论时直接输出;提示词明确指出知识库大分类层级对检索并无太大作用,切勿纠结分类或强行匹配目录,遇到报错时直接提取日志中的 Java Error 直接报错摘要(如Caused by:后的异常类名、报错消息文本、关键错误特征等)作为关键词直接检索;静态知识库精简聚焦于常见报错分析(patterns、日志分析)、日志格式速查(format)、Android 原生库(android-native-lib)以及手机启动器常识与 Minecraft 版本更新列表(mobile_launcher),移除了易过期的启动器实战与各 ModLoader/服务端开发文档,改由 GitHub 实时排障工具链提供动态支撑。- External HTTP / cURL calls (
AIClient,MCPClient,SemanticClient,GitHubClient) in resident Swoole processes must setCURLOPT_FORBID_REUSE => trueand explicitly release the handle ($ch = null) in afinallyblock to close sockets immediately; omitting this causes socket FD accumulation and "Too many open files" (EMFILE) outages under continuous traffic. Do not configure cURL options unsupported by Swoole coroutine hooks (such asCURLOPT_MAXFILESIZE/ option 114, which throws an unsupported exception and breaks batch processing; enforce payload caps in application code instead). - Request bodies supporting
Content-Encoding(br/gzip/deflate/zlib) for JSON requests are handled viaApp\Parser\RequestParser(registered as Hyperf'sRequestParserInterfaceinconfig/autoload/dependencies.php) to automatically decompress raw streams before sub-parsers run, with a 20MB decompression cap (MAX_DECOMPRESSED_BYTES) to prevent decompression bombs.App\ContentParsersimilarly supportsbr,gzip, anddeflatewith configured payload caps. - Server response compression: Swoole HTTP Server settings configure
'http_compression' => true(level 6, min_length 1024), which automatically and preferentially responds with Brotli (Content-Encoding: br) whenever the client sendsAccept-Encoding: br(defaulting to gzip when absent). - OpenResty compression configuration:
OpenLiteWaf/nginx/nginx.confanddocker/nginx/default.confconfiguregzip on(comp_level 6,gzip_proxied any) as baseline fallback, anddocker/nginx/default.confsetsproxy_set_header Accept-Encoding $http_accept_encodingto ensure client compression capabilities reach upstream. Brotli directives (brotli on; brotli_comp_level 6; ...) are pre-configured; when running on container images withngx_brotliloaded, uncomment thebrotliblock to enable edge-layer compression. Independent of whetherngx_brotliis loaded in OpenResty, Hyperf upstream responses natively deliver Brotli compression end-to-end via proxy pass-through. - 客户端接入与上传规范:客户端(各类启动器生态)上传时强烈建议同时提交「游戏主日志 + 崩溃报告 + 启动器日志」(通过
files数组或 ZIP 归档),并注明source来源标识(格式如pojav/3.4.0、fcl/1.2.0、zl2/2.1.0等);若请求体未显式提交source,服务端自动从 HTTPUser-Agent中提取并过滤规范的“启动器/版本”结构(排除通用浏览器与通用 HTTP 客户端黑名单);若未提供且 UA 非启动器格式,统一标为'未指定'(存储与 Admin 检索统一兜底为'未指定',支持按source=未指定联合过滤 NULL、空串与未指定记录);使 Admin 控制台能精准分类与检索启动器生态来源;上报公开遥测(POST /{version}/telemetry/report)支持 raw body 与 Beacon 兜底解析。 - If an API route or response changes, update
API.md,openapi.yaml, andpostman_collection.jsontogether.
- Version constant lives in
app/Version.php(App\Version::VERSION); keepREADME.mdandopenapi.yamlversion fields aligned when bumping. - Release flow: add a
## x.y.z — dateentry at the top ofCHANGELOG.md, alignapp/Version.php/README.md/openapi.yaml, then commit with a message starting with[Build]—.github/workflows/release.yamltriggers on that prefix, takes the tag (v+ version) and release notes from the first CHANGELOG entry.workflow_dispatchalso publishes. - Submodule commit order matters:
OpenLiteWaf/andOpenLiteStats/are separate GitHub repos (NingZeStudio org). Commit and push the change inside the submodule repo FIRST, then update the submodule pointer in this repo as a separate commit (chore: 更新 OpenLiteWaf submodule 指针(…)is the established style). - PHP code is baked into the Docker image (only
Config.inc.php,.env,mappings/are mounted): server-side PHP changes require rebuilding thehyperfimage, not just a container restart. Compose commands need--env-file .env(required${MARIADB_PASSWORD:?}interpolation). 重建前若宿主机挂载了runtime/且开启了SCAN_CACHEABLE: "true",必须先执行rm -rf runtime/container清理扫描缓存,避免旧容器代理类锁死导致新代码不生效。
- 使用简体中文与用户交流与推理;术语可保留英文,但表述须以中文为准。
- 开发环境是 Termux(Android):系统
/tmp只读,临时文件一律使用/data/data/com.termux/files/usr/tmp/或项目根tmp/;遇兼容性问题先用 WebSearch 检索,仍无法确定时向用户确认,不得直接执行未经验证的操作。 - 编写后端或前后端交互代码时,对 SQL 注入、XSS、CSRF、路径遍历、敏感信息泄露保持警惕;发现潜在风险须明确告知用户并征询处理意见,不得擅自忽略或掩盖。
- 严禁破坏用户全局环境;任何可能影响系统稳定性、数据完整性或安全性的危险操作,必须事先征得用户明确授权。
- 不懂就问、不妄加揣测:与文档或用户意图有出入时先确认再动手。
- 本文档是项目上下文的唯一约定来源:修改内容与之不符时,同步更新本文档。
- 文档与文案要求书面化的开发者文档风格(平铺直叙、不该省的字眼不要省),禁止营销腔与卖点罗列。
/security拦截页的卡片组件设计(橙色 #ea580c、三角形 SVG、"我们认为您的请求是恶意的")是用户亲自提供的设计,改动前先征询。- 生产环境是 api.logshare.cn(服务器上直接改过
docker/nginx/default.conf的域名与证书路径,仓库内仍是 api.test.logshare.cn,该差异是否合入待用户决定,不要擅自"修正");仓库即线上,用户会直接在服务器上改文件,改动时注意同步。 LOCAL_DEV_NOTES.md是仅存本地的会话恢复笔记(被.git/info/exclude排除,永不提交),包含线上 SSH 环境、fish shell、容器名等运维细节,可参考但不可提交。