feat(core): services.yaml 启动配置 + _run.sh 统一参数透传

Step 1: init.txt → services.yaml
- 新增 InitParser (yaml-cpp), 支持 YAML/TXT 双模式解析, 向后兼容 init.txt
- 新增 services.yaml 声明式服务配置 (name/cmd/args/depends_on/sleep_before)
- 改造 pmc_mtd.cpp 集成 InitParser, 支持目录/.yaml/.txt 三种路径模式
- Makefile 添加 InitParser 编译目标 + -lyaml-cpp 链接
- setup 脚本添加 libyaml-cpp-dev / yaml-cpp-devel 依赖
- core/_run.sh 改为 -i . (目录模式, 自动查找 services.yaml → init.txt)

Step 2: 11 个 _run.sh 统一参数透传
- 8 个脚本改动: 追加 "$@" / 移除 sleep / 修引号
- waf 保留 -c config.yaml 作为脚本默认值
- services.yaml waf args 改为 [] 避免重复传递
- 开发方案文档: docs/reports/core_services_yaml_dev_doc.md
This commit is contained in:
qing committed 2026-08-10 03:16:28 +08:00
1 parent bba7ee3eb4
commit 08df3929b2
18 files changed
+1144 -42

No files matched your search

+8 -3
View File
@@ -4,7 +4,7 @@ APP_NAME := nidus
INSTL_DIRS := /usr/local/bin
CONF_NAME := init.txt
CONF_NAME := services.yaml
CONF_DIR := /etc/Nidus
@@ -17,7 +17,7 @@ CPP:= g++
INCS:= -I include/
MY_LIBS := -L/usr/lib -lus_th -lus_http -lus_httpclient -lus_msg -lcurl
LIBS := -lpthread -lssl -lcrypto -lboost_program_options -lboost_json -lboost_thread -lboost_filesystem
LIBS := -lpthread -lssl -lcrypto -lboost_program_options -lboost_json -lboost_thread -lboost_filesystem -lyaml-cpp
CPPFLAGS := --std=c++20 -O3 -pipe #-Wall -Werror
@@ -26,7 +26,7 @@ $(shell mkdir -p $(BUILD_DIR))
#--------
all: ${APP_NAME}.out
${APP_NAME}.out: $(BUILD_DIR)/main.o $(BUILD_DIR)/PPool.o $(BUILD_DIR)/ProcessTask.o $(BUILD_DIR)/HttpTask.o $(BUILD_DIR)/ConfsTask.o $(BUILD_DIR)/Tttask.o $(BUILD_DIR)/pmc_mtd.o $(BUILD_DIR)/subsys_call.o
${APP_NAME}.out: $(BUILD_DIR)/main.o $(BUILD_DIR)/PPool.o $(BUILD_DIR)/ProcessTask.o $(BUILD_DIR)/HttpTask.o $(BUILD_DIR)/ConfsTask.o $(BUILD_DIR)/Tttask.o $(BUILD_DIR)/pmc_mtd.o $(BUILD_DIR)/subsys_call.o $(BUILD_DIR)/InitParser.o
$(CPP) $^ $(MY_LIBS) $(LIBS) -o $@
$(BUILD_DIR)/main.o: src/main.cpp
@@ -53,6 +53,9 @@ $(BUILD_DIR)/pmc_mtd.o: src/main/pmc_mtd.cpp
$(BUILD_DIR)/subsys_call.o: src/main/subsys_call.cpp
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
$(BUILD_DIR)/InitParser.o: src/registry/InitParser.cpp
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
install: $(APP_NAME).out
@@ -65,6 +68,7 @@ install: $(APP_NAME).out
@echo "正在配置 $(APP_NAME)"
@mkdir -p $(CONF_DIR)
@cp $(CONF_NAME) $(CONF_DIR)/$(CONF_NAME)
@cp init.txt $(CONF_DIR)/init.txt
@cp $(SRV_NAME) $(SRV_DIR)/$(SRV_NAME)
@systemctl daemon-reload
@systemctl enable $(SRV_NAME)
@@ -77,6 +81,7 @@ uninstall:
@
@rm -f $(INSTL_DIRS)/$(APP_NAME)
@rm -f $(CONF_DIRS)/$(CONF_NAME)
@rm -f $(CONF_DIRS)/init.txt
@rm -f -d ${CONF_DIRS}
@
@systemctl stop $(SRV_NAME)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
export LD_LIBRARY_PATH=/lib:$LD_LIBRARY_PATH
./nidus.out -i init.txt --serve 5200 --confs 8010
./nidus.out -i . --serve 5200 --confs 8010
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <string>
#include <vector>
#include <map>
namespace qing::registry {
/* 服务启动配置项
*
* 从 services.yaml 或 init.txt 解析出的单个服务声明。
* YAML 模式下包含完整的元数据;TXT 模式下仅 cmd 有值。
*/
struct ServiceConfig {
std::string name; /* 服务唯一标识 */
std::string cmd; /* 启动命令(如 bash /opt/.../_run.sh) */
std::vector<std::string> args; /* 传递给 _run.sh 的命令行参数 */
std::map<std::string, std::string> env; /* 启动前设置的环境变量 */
std::vector<std::string> depends_on; /* 依赖的服务名列表(未来 DAG 排序用) */
int sleep_before = 0; /* 启动前等待秒数 */
/* 拼接完整命令:cmd + " " + args... */
std::string toCommand() const;
};
/* 启动配置解析器
*
* 优先读 services.yaml(结构化声明),不存在则回退 init.txt(纯命令列表)。
* 两种格式都输出 std::vector<ServiceConfig>,调用方无需关心来源。
*/
class InitParser {
public:
/* 入口:传入配置目录(如 /etc/Nidus)
*
* 查找顺序:
* 1. <dir>/services.yaml → parseYaml()
* 2. <dir>/init.txt → parseTxt()
* 3. 都不存在 → 返回空 vector,调用方自行处理
*/
static std::vector<ServiceConfig> load(const std::string& dir);
/* 解析 services.yaml */
static std::vector<ServiceConfig> parseYaml(const std::string& path);
/* 解析 init.txt(向后兼容,每行一条命令,# 开头为注释) */
static std::vector<ServiceConfig> parseTxt(const std::string& path);
};
} // namespace qing::registry
+87
View File
@@ -0,0 +1,87 @@
# core/services.yaml
# 声明式服务启动配置(替代 init.txt)
# 如果本文件存在则优先使用,否则回退 init.txt
#
# 字段说明:
# name 服务唯一标识(用于日志和未来注册中心)
# cmd 启动命令(与 init.txt 每行格式一致)
# args 传递给 _run.sh 的命令行参数列表(可选)
# env 启动前设置的环境变量(可选)
# depends_on 依赖的服务名列表(未来 DAG 拓扑排序用,当前仅记录)
# sleep_before 启动前等待秒数(替代散落在 _run.sh 中的 sleep)
services:
# --- 数据层(最先启动) ---
- name: db2
cmd: bash /opt/micro_servers/db2/_run.sh
args: []
depends_on: []
sleep_before: 0
# --- AI 审核 ---
- name: ai_review
cmd: bash /opt/micro_servers/ai_review/_run.sh
args: []
depends_on: [db2]
sleep_before: 2
# --- 文件服务 ---
- name: dnfs2
cmd: bash /opt/micro_servers/dnfs2/_run.sh
args: []
depends_on: [db2, ai_review]
sleep_before: 0
# --- 邮件服务 ---
- name: smtp
cmd: bash /opt/micro_servers/smtp/_run.sh
args: []
depends_on: []
sleep_before: 2
# --- 前端静态资源 ---
- name: fsys
cmd: bash /opt/micro_servers/fsys/_run.sh
args: []
depends_on: []
sleep_before: 0
# --- 安全链路 ---
- name: waf
cmd: bash /opt/micro_servers/waf/_run.sh
args: [] # -c config.yaml 保留在 _run.sh 作为默认值
depends_on: [ca, gateway]
sleep_before: 0
- name: ca
cmd: bash /opt/micro_servers/ca/_run.sh
args: []
depends_on: [db2]
sleep_before: 2
- name: gateway
cmd: bash /opt/micro_servers/gateway/_run.sh
args: []
depends_on: [db2]
sleep_before: 2
# --- 验证码 ---
- name: captcha2
cmd: bash /opt/micro_servers/captcha2/_run.sh
args: []
depends_on: []
sleep_before: 1
# --- AI 工具箱 ---
- name: aibox
cmd: bash /opt/micro_servers/aibox/_run.sh
args: []
depends_on: []
sleep_before: 0
# --- 端口重定向(最后启动) ---
- name: redirect80
cmd: bash /opt/micro_servers/redirect80/_run.sh
args: []
depends_on: [waf]
sleep_before: 0
+1 -1
View File
@@ -149,7 +149,7 @@ int main(int argc, char** argv) {
("list", "list process all run") /* 列出运行中的进程 */
("kill", po::value<int>(), "kill a process") /* 杀死一个进程 */
/********* 子系统服务模式 ********/
("init,i", po::value<std::string>(), "self-init list") /* 输入自启动列表 */
("init,i", po::value<std::string>(), "self-init config (dir or .yaml/.txt file)") /* 输入自启动配置 */
("serve", po::value<int>(), "Run as http server") /* 启用http伺服器 */
("confs", po::value<int>(), "Run embedded confs rpc server"); /* 启用内嵌confs配置服务 */
+51 -21
View File
@@ -5,6 +5,12 @@
#include <boost/property_tree/json_parser.hpp>
#include "main/PPool.hh"
#include "main/pmc_mtd.hh"
#include "registry/InitParser.hh"
#include <unistd.h>
#include <thread>
#include <chrono>
#include <filesystem>
namespace qing {
@@ -87,37 +93,61 @@ catch(std::exception& exp) {
/* 解析自启动列表、添加自启动项
*
* 如果在解析过程中出错,只是打印异常? */
void parse_self_init_list(std::string &path)
* 兼容两种格式:
* - services.yaml(结构化声明,含 args/env/depends_on/sleep_before)
* - init.txt(纯命令列表,每行一条 bash 命令,# 开头为注释)
*
* path 可以是:
* - 目录路径 → 自动查找 services.yaml 或 init.txt
* - .yaml 文件 → 按 YAML 解析
* - 其他文件 → 按 TXT 解析(向后兼容 init.txt)
*
* 如果在解析过程中出错,只是打印异常 */
void parse_self_init_list(std::string &path)
try {
namespace fs = std::filesystem;
std::ifstream file(path);
if (!file.is_open()) { /* ---- 没能打开自启动文件 ---- */
std::cerr << "Failed to open self-init list" << std::endl;
std::vector<registry::ServiceConfig> services;
if (fs::is_directory(path)) {
/* 目录模式:InitParser::load 自动查找 services.yaml → init.txt */
services = registry::InitParser::load(path);
} else if (path.size() > 5 && path.substr(path.size() - 5) == ".yaml") {
/* YAML 文件模式 */
services = registry::InitParser::parseYaml(path);
} else {
/* TXT 文件模式(向后兼容 init.txt) */
services = registry::InitParser::parseTxt(path);
}
if (services.empty()) {
std::cerr << "parse_self_init_list: 没有可启动的服务" << std::endl;
return;
}
std::string row = "";
char ch;
while (file.get(ch))
if (ch == '\n') {
auto pos = row.find("#");
if (pos != -1)
row = row.substr(0, pos);
if (row != "") { /* trim() */
ppool.clr();
ppool.crtp(row); /* 创建进程 */
row = "";
}
for (const auto& svc : services) {
/* 启动前等待(替代散落在 _run.sh 中的 sleep) */
if (svc.sleep_before > 0) {
std::cerr << "[pmc] " << svc.name
<< " 等待 " << svc.sleep_before << "s..." << std::endl;
std::this_thread::sleep_for(
std::chrono::seconds(svc.sleep_before));
}
else row += ch;
/* 设置环境变量 */
for (const auto& [k, v] : svc.env)
setenv(k.c_str(), v.c_str(), 1);
file.close();
/* 清理已停止的进程,然后创建新进程 */
ppool.clr();
std::string full_cmd = svc.toCommand();
std::cerr << "[pmc] 启动: " << svc.name
<< " -> " << full_cmd << std::endl;
ppool.crtp(full_cmd);
}
}
catch (std::exception &exp) {
std::cerr << "Exception at: parse_self_init_list()" << std::endl;
std::cerr << "Exception type: " << typeid(exp).name() << std::endl;
+131
View File
@@ -0,0 +1,131 @@
#include "registry/InitParser.hh"
#include <yaml-cpp/yaml.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <filesystem>
namespace qing::registry {
/* -------- ServiceConfig -------- */
std::string ServiceConfig::toCommand() const {
std::ostringstream oss;
oss << cmd;
for (const auto& a : args) {
oss << ' ' << a;
}
return oss.str();
}
/* -------- InitParser -------- */
std::vector<ServiceConfig> InitParser::load(const std::string& dir) {
namespace fs = std::filesystem;
auto yaml_path = fs::path(dir) / "services.yaml";
auto txt_path = fs::path(dir) / "init.txt";
/* 优先 services.yaml */
if (fs::exists(yaml_path)) {
std::cerr << "[InitParser] 使用 services.yaml: " << yaml_path << std::endl;
return parseYaml(yaml_path.string());
}
/* 回退 init.txt */
if (fs::exists(txt_path)) {
std::cerr << "[InitParser] 回退 init.txt: " << txt_path << std::endl;
return parseTxt(txt_path.string());
}
std::cerr << "[InitParser] 未找到 services.yaml 或 init.txt 于 " << dir << std::endl;
return {};
}
std::vector<ServiceConfig> InitParser::parseYaml(const std::string& path) {
std::vector<ServiceConfig> result;
YAML::Node root;
try {
root = YAML::LoadFile(path);
} catch (const std::exception& e) {
std::cerr << "[InitParser] YAML 解析失败: " << e.what() << std::endl;
return {};
}
if (!root["services"] || !root["services"].IsSequence()) {
std::cerr << "[InitParser] services.yaml 缺少 services 序列" << std::endl;
return {};
}
for (const auto& node : root["services"]) {
ServiceConfig svc;
if (node["name"])
svc.name = node["name"].as<std::string>();
if (node["cmd"])
svc.cmd = node["cmd"].as<std::string>();
if (node["args"] && node["args"].IsSequence())
for (const auto& a : node["args"])
svc.args.push_back(a.as<std::string>());
if (node["env"] && node["env"].IsMap())
for (const auto& kv : node["env"])
svc.env[kv.first.as<std::string>()] = kv.second.as<std::string>();
if (node["depends_on"] && node["depends_on"].IsSequence())
for (const auto& d : node["depends_on"])
svc.depends_on.push_back(d.as<std::string>());
if (node["sleep_before"])
svc.sleep_before = node["sleep_before"].as<int>(0);
if (svc.cmd.empty()) {
std::cerr << "[InitParser] 跳过缺少 cmd 的服务: " << svc.name << std::endl;
continue;
}
result.push_back(std::move(svc));
}
std::cerr << "[InitParser] 解析完成,共 " << result.size() << " 个服务" << std::endl;
return result;
}
std::vector<ServiceConfig> InitParser::parseTxt(const std::string& path) {
std::vector<ServiceConfig> result;
std::ifstream file(path);
if (!file.is_open()) {
std::cerr << "[InitParser] 无法打开 init.txt: " << path << std::endl;
return {};
}
std::string line;
while (std::getline(file, line)) {
/* 去除注释(# 之后的内容) */
auto pos = line.find('#');
if (pos != std::string::npos)
line = line.substr(0, pos);
/* trim 空白 */
auto first = line.find_first_not_of(" \t\r\n");
if (first == std::string::npos)
continue; /* 空行 */
auto last = line.find_last_not_of(" \t\r\n");
line = line.substr(first, last - first + 1);
ServiceConfig svc;
svc.cmd = line;
/* init.txt 模式下 name/args/env/depends_on/sleep_before 均为空/默认 */
result.push_back(std::move(svc));
}
std::cerr << "[InitParser] init.txt 解析完成,共 " << result.size() << " 个服务" << std::endl;
return result;
}
} // namespace qing::registry