diff --git a/confs/README.md b/confs/README.md index f9295fb..1a393d0 100644 --- a/confs/README.md +++ b/confs/README.md @@ -227,7 +227,7 @@ except RpcError as e: ## 集成关系 -- 由 `core`(`PMC`)通过 `init.txt` 中的 `bash /opt/micro_servers/confs/_run.sh` 拉起并守护(需先于 db 启动,db 对 confs 有强依赖) +- 由 `core`(`PMC`)通过 `init.txt` 中的 `bash /opt/micro_servers/confs/_run.sh` 拉起并守护(confs 最先启动,所有依赖 confs 的服务必须在 confs 就绪后启动;confs 日志推送 db 为异步 fail-safe,db 不可用时静默丢弃,不阻塞 confs 启动) - 各内部服务通过 pymsg `RpcClient` 调 `get` 路由按需获取配置,替代本地散落的 ini/json/yaml - 自身日志通过 `log_client.py` 异步推送至 `db:8005` 的 Logs 表(与其他服务一致) @@ -235,15 +235,17 @@ except RpcError as e: | 服务 | 配置文件 | 接入方式 | |------|---------|---------| -| [static](../static/) | [static.ini](configs/static.ini) | 三层 fail-safe:内置默认值 → confs(优先)→ 本地 `config.ini` 回退 | +| [static](../static/) | [static.ini](configs/static.ini) | confs 强依赖(confs 不可用时 static 直接退出)→ 环境变量覆盖(最高优先级,Docker 兼容) | | [dnfs](../dnfs/) | [dnfs.ini](configs/dnfs.ini) | confs 强依赖(confs 不可用时 dnfs 直接退出)→ 环境变量覆盖(最高优先级,Docker 兼容) | | [db](../db/) | [db.ini](configs/db.ini) | confs 强依赖(confs 不可用时 db 直接退出)→ 环境变量覆盖(最高优先级,Docker 兼容) | static 的接入实现见 [static/fsys.py](../static/fsys.py): -- `_fetch_from_confs()` 通过 `RpcClient` 调 `get` 路由取 `static.ini`,3 秒超时 -- `CONFS_HOST` / `CONFS_PORT` 环境变量定位 confs(默认 `127.0.0.1:8010`) -- `_merge_ini()` 仅覆盖已知的 section/key(白名单合并,不引入未预期字段) -- 配置来源会记录到日志(`logs.info/warning`),便于排查实际用了哪一层 +- 两层加载顺序:confs(static.ini,强依赖)→ 环境变量覆盖(优先级最高) +- confs 连接失败或 static.ini 缺失必需配置项时,static 直接 `sys.exit(1)` 终止,不使用默认值或回退本地文件 +- 配置项涵盖:`[server]` host/port、`[directory]` root、`[ssl]` enabled/cert_file/key_file(ssl 有默认值)、`[cors]` allowed_origins +- confs 连接地址为引导配置:环境变量 `CONFS_HOST`/`CONFS_PORT` 优先,默认 `127.0.0.1:8010` +- 支持的环境变量:`LISTEN_ADDR`/`LISTEN_PORT`/`STATIC_ROOT`/`CONFS_HOST`/`CONFS_PORT` +- 启动顺序:confs 必须先于 static 启动;已删除 `--config` 命令行参数和本地 `config.ini` 回退逻辑 dnfs 的接入实现见 [dnfs/dnfs.py](../dnfs/dnfs.py): - 两层加载顺序:confs(dnfs.ini,强依赖)→ 环境变量覆盖(优先级最高) diff --git a/static/README.md b/static/README.md index 06ac0d7..ec49623 100644 --- a/static/README.md +++ b/static/README.md @@ -90,26 +90,52 @@ static :9200 (Flask + CORS) ## 配置 -`config.ini`: +static 已接入 confs 统一配置服务,confs 是 static 启动的**强依赖**: -```ini -[server] -host = 127.0.0.1 -port = 9200 +1. **confs 统一配置服务** — 必须成功从 confs 拉取 `static.ini`,否则 static 启动失败直接退出(exit code 1) +2. **环境变量覆盖**(最高优先级)— Docker/部署环境通过环境变量覆盖 confs 中的值 -[directory] -root = ./public +### 配置文件 -[ssl] -enabled = false -cert_file = cert.pem -key_file = key.pem +集中托管在 [confs/configs/static.ini](../confs/configs/static.ini),由 confs 服务分发: -[cors] -# 允许跨域的 Origin 白名单(逗号分隔) -# 注意:生产环境请勿包含 localhost / 127.0.0.1 / 通配符 * -allowed_origins = https://qsont.me,https://yhdet.top -``` +| Section | Key | 说明 | +|---------|-----|------| +| `[server]` | `host` | 监听地址 | +| `[server]` | `port` | 监听端口 | +| `[directory]` | `root` | 静态文件根目录(相对 BASE_DIR) | +| `[ssl]` | `enabled` | 是否启用 TLS(`true`/`false`,默认 `false`) | +| `[ssl]` | `cert_file` | SSL 证书路径(默认 `cert.pem`) | +| `[ssl]` | `key_file` | SSL 私钥路径(默认 `key.pem`) | +| `[cors]` | `allowed_origins` | CORS 白名单,逗号分隔 | + +### 引导配置(confs 连接地址) + +confs 自身的连接地址不通过 confs 获取(避免循环依赖),通过以下方式确定(优先级从高到低): +- 环境变量 `CONFS_HOST` / `CONFS_PORT` +- 默认值 `127.0.0.1:8010` + +### 环境变量(最高优先级,覆盖 confs 配置值) + +| 变量 | 说明 | +|------|------| +| `LISTEN_ADDR` | 监听地址(容器内需绑定 `0.0.0.0`) | +| `LISTEN_PORT` | 监听端口 | +| `STATIC_ROOT` | 静态文件根目录 | +| `CONFS_HOST` | confs 服务主机名(引导配置,Docker 环境使用服务名) | +| `CONFS_PORT` | confs 服务端口(引导配置) | + +### 启动顺序要求 + +**confs 必须先于 static 启动**。若 confs 不可用: +- static 打印致命错误到 stderr(包含 confs 地址和具体错误原因),以 exit code 1 终止 +- 不会静默使用默认值或回退本地配置文件启动 +- 配置完整性校验:若 static.ini 中缺少必需的 section/key(server.host/port、directory.root、cors.allowed_origins)且无对应环境变量覆盖,同样报错退出 +- ssl 段为可选项,未配置时默认 `enabled=false`、cert/key 使用默认文件名 + +### 历史遗留:本地 config.ini 已删除 + +旧版本的 `static/config.ini` 本地回退逻辑已移除,配置统一由 confs 服务分发。 ## 依赖 diff --git a/static/_run.sh b/static/_run.sh index 1f44b63..dc49454 100644 --- a/static/_run.sh +++ b/static/_run.sh @@ -1,5 +1,5 @@ #!/bin/bash cd "$(dirname "$0")" || exit source ./venv/bin/activate -#exec ./fsys.bin --config config.ini 1>/dev/null -exec python3 ./fsys.py --config config.ini 1>/dev/null +#exec ./fsys.bin 1>/dev/null +exec python3 ./fsys.py 1>/dev/null diff --git a/static/config.ini b/static/config.ini deleted file mode 100644 index fbccc07..0000000 --- a/static/config.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/static/fsys.py b/static/fsys.py index f2542e2..ae56e99 100644 --- a/static/fsys.py +++ b/static/fsys.py @@ -36,33 +36,35 @@ file_handler.setLevel(logging.INFO) logger.addHandler(file_handler) parser = argparse.ArgumentParser(description='File Server') -parser.add_argument('--config', type=str, default='config.ini', help='Configuration file path') args = parser.parse_args() app = Flask(__name__) apply_security_headers(app) -# ==================== 配置文件加载 ==================== -# 加载顺序:内置默认值 → confs 统一配置服务(static.ini); -# confs 不可用时 fail-safe 回退本地 config.ini -CONFIG_FILE = args.config -if not os.path.isabs(CONFIG_FILE): - CONFIG_FILE = os.path.join(BASE_DIR, CONFIG_FILE) +# ==================== 配置加载(两层:confs 强依赖 → 环境变量覆盖) ==================== +# confs 是 static 启动的强依赖:必须成功从 confs 拉取 static.ini,否则直接退出。 +# 环境变量优先级最高(Docker 兼容),可覆盖 confs 中的配置值。 +# confs 自身的连接地址通过引导配置确定:环境变量 CONFS_HOST/CONFS_PORT 优先, +# 缺省使用 127.0.0.1:8010(与其他服务一致的约定)。 +# 引导配置:confs 连接地址(不依赖 confs 本身) +_confs_host = os.environ.get('CONFS_HOST', '127.0.0.1') +_confs_port = int(os.environ.get('CONFS_PORT', '8010')) + +# 白名单结构:_merge_ini 仅覆盖此处列出的 section/key,防止意外字段 file_config = { - 'server': {'host': '0.0.0.0', 'port': '5000'}, - 'directory': {'root': './public'}, - 'ssl': {'enabled': 'false', 'cert_file': 'cert.pem', 'key_file': 'key.pem'}, - # CORS 白名单(逗号分隔),生产环境默认值;localhost 仅限本地开发时显式写入 config.ini - 'cors': {'allowed_origins': 'https://qsont.me,https://yhdet.top'}, + 'server': {'host': '', 'port': ''}, + 'directory': {'root': ''}, + 'ssl': {'enabled': '', 'cert_file': '', 'key_file': ''}, + 'cors': {'allowed_origins': ''}, } def _merge_ini(text): """把 ini 文本合并进 file_config(仅覆盖已知的 section/key)。""" - ini = configparser.ConfigParser() + ini = configparser.ConfigParser(interpolation=None) ini.read_string(text) - for section in ['server', 'directory', 'ssl', 'cors']: + for section in file_config: if ini.has_section(section): for key, value in ini[section].items(): if key in file_config[section]: @@ -70,28 +72,64 @@ def _merge_ini(text): def _fetch_from_confs(): - """从 confs 统一配置服务拉取 static.ini 全文,失败返回 None(fail-safe)。""" + """从 confs 统一配置服务拉取 static.ini 全文,失败直接终止进程。""" + from pymsg import RpcClient, RpcError # noqa: E402 try: - from pymsg import RpcClient - host = os.environ.get('CONFS_HOST', '127.0.0.1') - port = int(os.environ.get('CONFS_PORT', '8010')) - data = RpcClient(host, port, 'static', timeout=3).call( - 'get', {'name': 'static.ini'}) + client = RpcClient(_confs_host, _confs_port, 'static', timeout=5) + data = client.call('get', {'name': 'static.ini'}) return data.get('content') - except Exception: - return None + except (ConnectionRefusedError, ConnectionError, OSError) as e: + print(f'[static] 致命错误: 无法连接 confs 服务 ({_confs_host}:{_confs_port}): {e}', + file=sys.stderr) + print(f'[static] 请确认 confs 服务已启动并监听 {_confs_host}:{_confs_port}', + file=sys.stderr) + sys.exit(1) + except RpcError as e: + print(f'[static] 致命错误: 从 confs 获取 static.ini 失败: {e}', + file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f'[static] 致命错误: confs 配置拉取异常: {e}', + file=sys.stderr) + sys.exit(1) _confs_text = _fetch_from_confs() -if _confs_text is not None: - _merge_ini(_confs_text) - logger.info("配置来源: confs 统一配置服务 (static.ini)") - logs.info("static", "配置来源: confs 统一配置服务 (static.ini)") -elif os.path.exists(CONFIG_FILE): - with open(CONFIG_FILE, encoding='utf-8') as f: - _merge_ini(f.read()) - logger.warning(f"confs 不可用,回退本地配置文件 {CONFIG_FILE}") - logs.warning("static", f"confs 不可用,回退本地配置文件 {CONFIG_FILE}") +_merge_ini(_confs_text) + +# 环境变量覆盖(优先级最高,Docker 兼容) +if os.environ.get('LISTEN_ADDR'): + file_config['server']['host'] = os.environ['LISTEN_ADDR'] +if os.environ.get('LISTEN_PORT'): + file_config['server']['port'] = os.environ['LISTEN_PORT'] +if os.environ.get('STATIC_ROOT'): + file_config['directory']['root'] = os.environ['STATIC_ROOT'] + +# 配置完整性校验 +_required = { + 'server': ['host', 'port'], + 'directory': ['root'], + 'cors': ['allowed_origins'], +} +for section, keys in _required.items(): + for key in keys: + if not file_config[section][key]: + print(f'[static] 致命错误: 配置项 [{section}] {key} 缺失(static.ini 中未配置且无环境变量覆盖)', + file=sys.stderr) + sys.exit(1) + +# ssl 段有合理默认(enabled=false,cert/key 有默认文件名),不强制要求配置 +if not file_config['ssl']['enabled']: + file_config['ssl']['enabled'] = 'false' +if not file_config['ssl']['cert_file']: + file_config['ssl']['cert_file'] = 'cert.pem' +if not file_config['ssl']['key_file']: + file_config['ssl']['key_file'] = 'key.pem' + +_config_source = f'confs (static.ini @ {_confs_host}:{_confs_port})' +print(f'[static] 配置来源: {_config_source}') +logger.info(f"配置来源: {_config_source}") +logs.info("static", f"配置来源: {_config_source}") # ==================== CORS(配置加载完成后初始化) ==================== _cors_origins = [o.strip() for o in file_config['cors']['allowed_origins'].split(',') if o.strip()]