From c9e8d04815b3389ab8da59cabd7c5240f8a6f57f Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Mon, 10 Aug 2026 10:08:29 +0000 Subject: [PATCH] =?UTF-8?q?fsys:=20=E8=BF=81=E7=A7=BB=20static.ini=20?= =?UTF-8?q?=E8=87=B3=20CLI=20args=EF=BC=8C=E7=A7=BB=E9=99=A4=20confs=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fsys/src/fsys.cpp: 移除 parse_ini/IniMap/confs RPC 调用,改为 boost::program_options 14 项 CLI 参数,配置优先级 CLI > env > 默认值 - fsys/Makefile: 添加 -lboost_program_options - core/services.yaml: fsys 全部参数迁入 CLI args - confs/configs/static.ini: 已删除 - README.md: 更新 fsys 配置说明,移除 static.ini 表行,更新安全约定 --- README.md | 5 +- confs/configs/static.ini | 17 --- core/services.yaml | 29 ++++- fsys/Makefile | 2 +- fsys/src/fsys.cpp | 239 +++++++++++---------------------------- 5 files changed, 94 insertions(+), 198 deletions(-) delete mode 100644 confs/configs/static.ini diff --git a/README.md b/README.md index 7d27e36..21bc73d 100755 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ - **账号旁路**:`/api/check_user`、`/api/reset_password` 等接口直接读写 `gateway/sqlite3.db` 的 Users 表;`/yhdet_callback`、`/api/yhdet_*` 对接 yhdet.top OAuth 登录。 - 统一品牌"树状菜单",支持深色模式(`css/theme.css`)。 - 可使用 Nuitka 编译为独立二进制(`fsys.bin`)。 -- 配置由 `confs` 统一配置服务分发(`confs/configs/static.ini`,启动时经 pymsg 拉取,`CONFS_HOST`/`CONFS_PORT` 环境变量可覆盖);confs 为**强依赖**,拉取失败直接退出,无本地回退。 +- 配置全部经 CLI 传递(CLI > 环境变量 > 默认值,原 `confs/configs/static.ini` 迁入 `core/services.yaml` 的 fsys 条目 args),不依赖 confs。 ### dnfs(树状菜单服务) @@ -208,10 +208,9 @@ | 配置文件 | 消费模块 | 依赖强度 | |----------|----------|----------| | `gateway.ini` | gateway | confs 拉取(0.7.0 起配置完全迁入) | -| `static.ini` | static(fsys.py)/ fsys | 强依赖(拉取失败直接退出) | | `example.ini` | —(旧 gateway 示例模板) | 无消费方 | -- **部署安全约定**:`confs/configs/*.ini` 含敏感字段,权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可劫持对应服务(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。原 `ca.ini` 管理员白名单已随 ca v1.1.0 迁入 `core/services.yaml` 的 CLI args;原 `smtp.ini` 全部配置已随 smtp3 迁入 `core/services.yaml` 的 CLI args(SMTP 凭据仍经 `.env` 注入),防护对象相应转为 `services.yaml` 与 `.env` 的文件权限。 +- **部署安全约定**:`confs/configs/*.ini` 含敏感字段,权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可劫持对应服务(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。原 `ca.ini` 管理员白名单已随 ca v1.1.0 迁入 `core/services.yaml` 的 CLI args;原 `smtp.ini` 全部配置已随 smtp3 迁入 `core/services.yaml` 的 CLI args(SMTP 凭据仍经 `.env` 注入);原 `captcha.ini` 与 `static.ini` 已分别随 captcha2、fsys 迁入 CLI args,防护对象相应转为 `services.yaml` 与 `.env` 的文件权限。 ### aibox(独立 AI 智能体系统) diff --git a/confs/configs/static.ini b/confs/configs/static.ini deleted file mode 100644 index fbccc07..0000000 --- a/confs/configs/static.ini +++ /dev/null @@ -1,17 +0,0 @@ -[server] -host = 127.0.0.1 -port = 9200 - -[directory] -root = ./public - -[ssl] -enabled = false -cert_file = cert.pem -key_file = key.pem - -[cors] -# 允许跨域的 Origin 白名单(逗号分隔) -# 注意:生产环境请勿包含 localhost / 127.0.0.1 / 通配符 * -# 本地开发联调时可临时追加 http://localhost:9200,调试完务必移除 -allowed_origins = https://qsont.me,https://yhdet.top diff --git a/core/services.yaml b/core/services.yaml index 69e01a4..03f839c 100644 --- a/core/services.yaml +++ b/core/services.yaml @@ -83,7 +83,34 @@ services: # --- 前端静态资源 --- - name: fsys cmd: bash /opt/micro_servers/fsys/_run.sh - args: ["--instance", "fsys", "--log-level", "info"] + args: + - "--instance" + - "fsys" + - "--log-level" + - "info" + # --- 配置全部经 CLI 传递(原 confs/configs/static.ini 迁入) --- + - "--addr" + - "127.0.0.1" + - "--port" + - "9200" + - "--threads" + - "4" + - "--root" + - "./public" + - "--ssl-enabled" + - "false" + - "--cert-file" + - "cert.pem" + - "--key-file" + - "key.pem" + - "--cors-origins" + - "https://qsont.me,https://yhdet.top" + - "--db-host" + - "127.0.0.1" + - "--db-port" + - "8005" + - "--dnfs-url" + - "http://127.0.0.1:8006" depends_on: [] sleep_before: 0 diff --git a/fsys/Makefile b/fsys/Makefile index 1420b80..9f76753 100644 --- a/fsys/Makefile +++ b/fsys/Makefile @@ -2,7 +2,7 @@ CPP = g++ CPPFLAGS = -std=c++20 -O3 -Wall -Werror -pipe INCS = -I ../libhttp/inc -I ../libmsg/inc -I /opt/homebrew/include LIBS = -L ../libhttp/build -L ../libmsg/build -L /opt/homebrew/lib \ - -lus_http -lus_httpclient -lus_msg -lboost_json \ + -lus_http -lus_httpclient -lus_msg -lboost_json -lboost_program_options \ -lssl -lcrypto -lcurl -lpthread RPATH = -Wl,-rpath,'$$ORIGIN/../../libhttp/build' \ -Wl,-rpath,'$$ORIGIN/../../libmsg/build' \ diff --git a/fsys/src/fsys.cpp b/fsys/src/fsys.cpp index b8723cf..36c004a 100644 --- a/fsys/src/fsys.cpp +++ b/fsys/src/fsys.cpp @@ -14,11 +14,11 @@ * 安全改进(相比 Python static): * F-1 server.setMaxBodySize(10MB) 限制请求体大小 * F-2 libhttp 原生 HTTPS 支持,无需回退开发服务器 - * F-3 命令行参数 --host/--addr/-a --port/-p --threads/-t --log-level --instance --config --help + * F-3 命令行参数 --addr/-a --port/-p --threads/-t * F-4 所有端口/线程数有严格边界校验 * F-5 Boost.Beast 异步多线程替代 Flask 同步 * F-6 消除 Python/Flask/waitress 依赖 - * F-7 日志客户端复用主配置(confs 拉取的 db host/port) + * F-7 日志客户端复用主配置(CLI 传递的 db host/port) * * 依赖: libhttp (HttpServer/HttpClient), libmsg (RpcClient/AsyncQueue), * boost::json, OpenSSL, libcurl @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -64,6 +65,7 @@ #include namespace json = boost::json; +namespace po = boost::program_options; namespace http = boost::beast::http; namespace fs = std::filesystem; using namespace pmc::net; @@ -209,44 +211,6 @@ static std::string extract_path(const std::string& target) { return (q == std::string::npos) ? target : target.substr(0, q); } -// ============================================================================ -// INI 解析(白名单模式,与 dnfs2 一致) -// ============================================================================ - -using IniMap = std::map>; - -static IniMap parse_ini(const std::string& text) { - IniMap result; - std::string section; - std::istringstream ss(text); - std::string line; - while (std::getline(ss, line)) { - auto b = line.find_first_not_of(" \t\r\n"); - if (b == std::string::npos) continue; - line = line.substr(b); - auto e = line.find_last_not_of(" \t\r\n"); - if (e != std::string::npos) line = line.substr(0, e + 1); - - if (line.empty() || line[0] == '#' || line[0] == ';') continue; - if (line.front() == '[' && line.back() == ']') { - section = line.substr(1, line.size() - 2); - continue; - } - auto eq = line.find('='); - if (eq == std::string::npos || section.empty()) continue; - std::string key = line.substr(0, eq); - std::string val = line.substr(eq + 1); - auto trim = [](std::string& s) { - auto bb = s.find_first_not_of(" \t"); - auto ee = s.find_last_not_of(" \t"); - s = (bb == std::string::npos) ? "" : s.substr(bb, ee - bb + 1); - }; - trim(key); trim(val); - result[section][key] = val; - } - return result; -} - // ============================================================================ // 配置 // ============================================================================ @@ -264,105 +228,43 @@ struct Config { std::vector cors_origins; - std::string confs_host = "127.0.0.1"; - int confs_port = 8010; - std::string db_host = "127.0.0.1"; int db_port = 8005; std::string dnfs_url = "http://127.0.0.1:8006"; - - // CLI 标准化参数(Step 3) - std::string log_level = "info"; // debug|info|warn|error - std::string instance_name = "fsys"; // 实例名(注册中心用) - std::string confs_config_name = "static.ini"; // confs 拉取的配置名 }; static Config load_config(int argc, char* argv[]) { Config cfg; - // 0. 预扫描 --help 和 --config(需在 confs 拉取前处理) - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if (arg == "--help" || arg == "-h") { - std::printf( - "fsys — C++20 静态文件托管服务\n\n" - "Usage: fsys [OPTIONS]\n\n" - "Options:\n" - " -h, --help Show this help and exit\n" - " -a, --addr Listen address (alias: --host)\n" - " --host Listen address (canonical name)\n" - " -p, --port Listen port\n" - " -t, --threads Worker threads\n" - " --config confs config name (default: static.ini)\n" - " --log-level debug|info|warn|error (default: info)\n" - " --instance Instance name (default: fsys)\n\n" - "Config priority: CLI > env > confs > defaults\n"); - std::exit(0); - } - if (arg == "--config" && i + 1 < argc) { - cfg.confs_config_name = argv[++i]; - } + // ── 命令行参数 ── + po::options_description desc("fsys — 静态文件托管服务"); + desc.add_options() + ("help,h", "显示帮助") + ("instance", po::value()->default_value("fsys"), "实例名") + ("log-level", po::value()->default_value("info"), "日志级别") + ("addr,a", po::value(), "监听地址") + ("port,p", po::value(), "监听端口") + ("threads,t", po::value(), "工作线程数") + ("root", po::value(), "静态文件根目录") + ("ssl-enabled", po::value(), "SSL 开关 (true/false)") + ("cert-file", po::value(), "SSL 证书路径") + ("key-file", po::value(), "SSL 私钥路径") + ("cors-origins", po::value(), "CORS 白名单(逗号分隔)") + ("db-host", po::value(), "数据库 RPC 地址") + ("db-port", po::value(), "数据库 RPC 端口") + ("dnfs-url", po::value(), "dnfs2 服务 URL"); + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << std::endl; + std::exit(0); } - // 1. confs 引导地址 - if (const char* h = std::getenv("CONFS_HOST")) cfg.confs_host = h; - if (const char* p = std::getenv("CONFS_PORT")) { - cfg.confs_port = safe_stoi(p, "CONFS_PORT"); - if (cfg.confs_port < 1 || cfg.confs_port > 65535) { - std::fprintf(stderr, "[fsys] invalid CONFS_PORT: %d (must be 1-65535)\n", cfg.confs_port); - std::exit(1); - } - } - - // 2. 从 confs 拉取配置(retry_on_refused=true) - std::string ini_text; - try { - RpcClient confs_client(cfg.confs_host, cfg.confs_port, "fsys", - /*timeout=*/5.0, /*retry_on_refused=*/true); - json::value data = confs_client.call("get", {{"name", cfg.confs_config_name}}); - if (data.is_object() && data.as_object().contains("content")) { - ini_text = std::string(data.as_object().at("content").as_string()); - } - } catch (const std::exception& e) { - std::fprintf(stderr, "[fsys] 从 confs 获取 %s 失败: %s\n", - cfg.confs_config_name.c_str(), e.what()); - std::exit(1); - } - - // 3. 解析 INI - IniMap ini = parse_ini(ini_text); - - auto get = [&](const std::string& sec, const std::string& key, - const std::string& def = "") -> std::string { - auto si = ini.find(sec); - if (si == ini.end()) return def; - auto ki = si->second.find(key); - return (ki == si->second.end()) ? def : ki->second; - }; - - cfg.listen_host = get("server", "host", cfg.listen_host); - cfg.listen_port = safe_stoi(get("server", "port", std::to_string(cfg.listen_port)), "[server] port"); - cfg.static_root = get("directory", "root", cfg.static_root); - - std::string ssl_str = get("ssl", "enabled", "false"); - cfg.ssl_enabled = (ssl_str == "true" || ssl_str == "True" || ssl_str == "1"); - cfg.ssl_cert = get("ssl", "cert_file", cfg.ssl_cert); - cfg.ssl_key = get("ssl", "key_file", cfg.ssl_key); - - std::string origins_str = get("cors", "allowed_origins", ""); - if (!origins_str.empty()) { - std::istringstream oss(origins_str); - std::string origin; - while (std::getline(oss, origin, ',')) { - auto b = origin.find_first_not_of(" \t"); - auto e = origin.find_last_not_of(" \t"); - if (b != std::string::npos) - cfg.cors_origins.push_back(origin.substr(b, e - b + 1)); - } - } - - // 4. 环境变量覆盖(Docker 兼容) + // ── 配置加载:CLI > 环境变量 > 默认值 ── + // 1. 环境变量覆盖(Docker 兼容) if (const char* v = std::getenv("LISTEN_ADDR")) cfg.listen_host = v; if (const char* v = std::getenv("LISTEN_PORT")) cfg.listen_port = safe_stoi(v, "LISTEN_PORT"); if (const char* v = std::getenv("STATIC_ROOT")) cfg.static_root = v; @@ -370,32 +272,49 @@ static Config load_config(int argc, char* argv[]) { if (const char* v = std::getenv("DB_PORT")) cfg.db_port = safe_stoi(v, "DB_PORT"); if (const char* v = std::getenv("DNFS_URL")) cfg.dnfs_url = v; - // 5. 命令行参数(最高优先级) - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if ((arg == "--addr" || arg == "-a" || arg == "--host") && i + 1 < argc) cfg.listen_host = argv[++i]; - else if ((arg == "--port" || arg == "-p") && i + 1 < argc) cfg.listen_port = safe_stoi(argv[++i], "--port"); - else if ((arg == "--threads" || arg == "-t") && i + 1 < argc) cfg.threads = safe_stoi(argv[++i], "--threads"); - else if (arg == "--log-level" && i + 1 < argc) cfg.log_level = argv[++i]; - else if (arg == "--instance" && i + 1 < argc) cfg.instance_name = argv[++i]; - // --config 和 --help 已在预扫描阶段处理 + // 2. CLI 参数覆盖(最高优先级) + if (vm.count("addr")) cfg.listen_host = vm["addr"].as(); + if (vm.count("port")) cfg.listen_port = vm["port"].as(); + if (vm.count("threads")) cfg.threads = vm["threads"].as(); + if (vm.count("root")) cfg.static_root = vm["root"].as(); + if (vm.count("ssl-enabled")) { + std::string s = vm["ssl-enabled"].as(); + cfg.ssl_enabled = (s == "true" || s == "True" || s == "1"); } + if (vm.count("cert-file")) cfg.ssl_cert = vm["cert-file"].as(); + if (vm.count("key-file")) cfg.ssl_key = vm["key-file"].as(); + if (vm.count("cors-origins")) { + std::string origins_str = vm["cors-origins"].as(); + if (!origins_str.empty()) { + std::istringstream oss(origins_str); + std::string origin; + while (std::getline(oss, origin, ',')) { + auto b = origin.find_first_not_of(" \t"); + auto e = origin.find_last_not_of(" \t"); + if (b != std::string::npos) + cfg.cors_origins.push_back(origin.substr(b, e - b + 1)); + } + } + } + if (vm.count("db-host")) cfg.db_host = vm["db-host"].as(); + if (vm.count("db-port")) cfg.db_port = vm["db-port"].as(); + if (vm.count("dnfs-url")) cfg.dnfs_url = vm["dnfs-url"].as(); - // 6. 校验必需配置项 + // 3. 校验必需配置项 if (cfg.listen_host.empty()) { - std::fprintf(stderr, "[fsys] 缺少必需配置: [server] host\n"); + std::fprintf(stderr, "[fsys] 缺少必需配置: --addr\n"); std::exit(1); } if (cfg.static_root.empty()) { - std::fprintf(stderr, "[fsys] 缺少必需配置: [directory] root\n"); + std::fprintf(stderr, "[fsys] 缺少必需配置: --root\n"); std::exit(1); } if (cfg.cors_origins.empty()) { - std::fprintf(stderr, "[fsys] 缺少必需配置: [cors] allowed_origins\n"); + std::fprintf(stderr, "[fsys] 缺少必需配置: --cors-origins\n"); std::exit(1); } - // 7. 数值范围校验 + // 4. 数值范围校验 if (cfg.listen_port < 1 || cfg.listen_port > 65535) { std::fprintf(stderr, "[fsys] invalid listen_port: %d (must be 1-65535)\n", cfg.listen_port); std::exit(1); @@ -405,19 +324,7 @@ static Config load_config(int argc, char* argv[]) { std::exit(1); } - // log_level 校验 - { - std::string ll = cfg.log_level; - std::transform(ll.begin(), ll.end(), ll.begin(), ::tolower); - if (ll != "debug" && ll != "info" && ll != "warn" && ll != "error") { - std::fprintf(stderr, "[fsys] invalid --log-level: %s (must be debug|info|warn|error)\n", - cfg.log_level.c_str()); - std::exit(1); - } - cfg.log_level = ll; - } - - // 8. 路径规范化:static_root/SSL 相对路径基于当前工作目录 + // 5. 路径规范化:static_root/SSL 相对路径基于当前工作目录 // (_run.sh 以模块目录为 cwd 启动,与 Python 版 BASE_DIR 语义一致; // Docker 场景使用绝对路径 STATIC_ROOT,不受影响) if (!cfg.static_root.empty() && cfg.static_root[0] != '/') { @@ -699,11 +606,7 @@ public: _filesystem_enabled = false; } - std::printf("[fsys] 配置来源: confs (%s @ %s:%d)\n", - _cfg.confs_config_name.c_str(), - _cfg.confs_host.c_str(), _cfg.confs_port); - std::printf("[fsys] 实例: %s | 日志级别: %s\n", - _cfg.instance_name.c_str(), _cfg.log_level.c_str()); + std::printf("[fsys] 配置来源: CLI > 环境变量 > 默认值\n"); std::printf("[fsys] 静态目录: %s\n", _cfg.static_root.c_str()); } @@ -735,19 +638,6 @@ private: void _log(const std::string& level, const std::string& service, const std::string& message, const std::string& trace_id = "", const std::string& extra = "") { - // 日志级别过滤 - static const std::unordered_map kLogLevels = { - {"debug", 0}, {"DEBUG", 0}, - {"info", 1}, {"INFO", 1}, - {"warn", 2}, {"WARNING", 2}, - {"error", 3}, {"ERROR", 3} - }; - auto cfg_it = kLogLevels.find(_cfg.log_level); - auto msg_it = kLogLevels.find(level); - if (cfg_it != kLogLevels.end() && msg_it != kLogLevels.end()) { - if (msg_it->second < cfg_it->second) return; - } - LogEntry e; e.ts = utc_now(); e.service = service; @@ -1637,9 +1527,6 @@ int main(int argc, char* argv[]) { cfg.static_root.c_str(), cfg.ssl_enabled ? "on" : "off"); std::printf("[fsys] db=%s:%d dnfs=%s\n", cfg.db_host.c_str(), cfg.db_port, cfg.dnfs_url.c_str()); - std::printf("[fsys] instance=%s log-level=%s config=%s\n", - cfg.instance_name.c_str(), cfg.log_level.c_str(), - cfg.confs_config_name.c_str()); std::fflush(stdout); FsysServer server(std::move(cfg));