1
0
Fork 0
mirror of https://github.com/adnanh/webhook.git synced 2026-07-20 16:53:47 +00:00

feat: 添加config 能力

命令行参数优先级高于配置文件(配置文件先加载,flag.Parse() 后执行会覆盖)
This commit is contained in:
jason.liao 2026-05-11 17:09:21 +08:00
parent 0645b1404f
commit cc338cd3ac
3 changed files with 287 additions and 0 deletions

118
config.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"gopkg.in/yaml.v2"
)
// configFile holds the path to the YAML configuration file.
// It is extracted from os.Args before flag.Parse() runs.
var configFile string
// loadConfigFile reads a YAML configuration file and sets the corresponding
// flags. Flags set via the command line take precedence over the config file
// because this function is called before flag.Parse().
//
// Supported YAML structure:
//
// ip: 0.0.0.0
// port: 9000
// nopanic: true
// hooks:
// - /etc/webhook/hooks.yaml
// header:
// - "X-Custom=value"
func loadConfigFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file %q: %w", path, err)
}
// Use a generic map so we can handle both scalar and list values.
var cfg map[string]interface{}
if err := yaml.Unmarshal(data, &cfg); err != nil {
return fmt.Errorf("failed to parse config file %q: %w", path, err)
}
for key, val := range cfg {
f := flag.CommandLine.Lookup(key)
if f == nil {
return fmt.Errorf("unknown option %q in config file %q", key, path)
}
switch v := val.(type) {
case []interface{}:
// List values: call flag.Set for each element (supports -hooks, -header, etc.)
for _, item := range v {
s := fmt.Sprintf("%v", item)
if err := flag.CommandLine.Set(key, s); err != nil {
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
}
}
case bool:
s := "false"
if v {
s = "true"
}
if err := flag.CommandLine.Set(key, s); err != nil {
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
}
default:
s := fmt.Sprintf("%v", v)
if err := flag.CommandLine.Set(key, s); err != nil {
return fmt.Errorf("error setting flag %q to %q: %w", key, s, err)
}
}
}
return nil
}
// extractConfigFlag scans os.Args for -config / -c / --config and returns the
// path value. It also removes those arguments from os.Args so that flag.Parse()
// does not see them (since -config is not a registered flag).
func extractConfigFlag() string {
var path string
var newArgs []string
args := os.Args[1:] // skip program name
newArgs = append(newArgs, os.Args[0])
for i := 0; i < len(args); i++ {
arg := args[i]
var matched bool
var value string
switch {
case arg == "-c" || arg == "-config" || arg == "--config":
matched = true
if i+1 < len(args) {
i++
value = args[i]
}
case strings.HasPrefix(arg, "-c="):
matched = true
value = strings.TrimPrefix(arg, "-c=")
case strings.HasPrefix(arg, "-config="):
matched = true
value = strings.TrimPrefix(arg, "-config=")
case strings.HasPrefix(arg, "--config="):
matched = true
value = strings.TrimPrefix(arg, "--config=")
}
if matched {
path = value
} else {
newArgs = append(newArgs, arg)
}
}
os.Args = newArgs
return path
}

View file

@ -99,6 +99,15 @@ func main() {
// register platform-specific flags
platformFlags()
// Extract -config / -c from os.Args before flag.Parse() sees it.
configFile = extractConfigFlag()
if configFile != "" {
if err := loadConfigFile(configFile); err != nil {
fmt.Println("error:", err)
os.Exit(1)
}
}
flag.Parse()
if err := initExecutionSettings(); err != nil {

160
webhook.yaml.example Normal file
View file

@ -0,0 +1,160 @@
# Webhook 配置文件
# 用法: webhook -c /etc/webhook/webhook.yaml
#
# 支持所有命令行参数。
# 列表类参数hooks、header使用 YAML 数组语法。
# 布尔类参数使用 true/false。
# 命令行参数优先级高于本配置文件。
# ============================================================
# 网络设置
# ============================================================
# 监听地址,默认 0.0.0.0(监听所有网卡)
ip: 0.0.0.0
# 监听端口,默认 9000
port: 1987
# URL 前缀,最终 hook 地址为 http://host:port/PREFIX/hook-id
# 默认值为 "hooks"
urlprefix: wh
# 限制允许的 HTTP 方法,多个方法用逗号分隔(如 "POST,PUT"
# 留空表示允许所有方法
# http-methods: "POST"
# ============================================================
# Hook 定义文件
# ============================================================
# hook 定义文件路径,支持 JSON 和 YAML 格式
# 可指定多个文件,每个文件中的 hook id 不能重复
hooks:
- /etc/webhook/admin.yaml
- /etc/webhook/hooks.yaml
# ============================================================
# 运行行为
# ============================================================
# 当无法加载任何 hook 时不要 panic 退出(需配合非 verbose 模式)
# 默认 false
nopanic: true
# 监视 hook 文件变化,自动热重载
# 默认 false
hotreload: true
# 将 hook 文件作为 Go template 解析
# 默认 false
# template: false
# multipart 表单解析时的最大内存(字节),超出部分写入磁盘临时文件
# 默认 1048576 (1MB)
# max-multipart-mem: 1048576
# ============================================================
# 日志设置
# ============================================================
# 启用详细日志输出
# 默认 false
# verbose: true
# 启用调试输出(会打印完整的请求/响应内容,隐含 verbose=true
# 默认 false
# debug: false
# 日志输出到文件(设置后隐含 verbose=true
# 留空表示输出到 stdout
# logfile: /var/log/webhook.log
# ============================================================
# 自定义响应头
# ============================================================
# 所有 hook 响应都会附带这些 header格式为 "Name=Value"
# header:
# - "X-Custom-Header=value"
# - "Access-Control-Allow-Origin=*"
# ============================================================
# Request ID
# ============================================================
# 使用客户端传入的 X-Request-Id 头作为请求 ID用于日志追踪
# 默认 false
# x-request-id: false
# 截断 X-Request-Id 的最大长度0 表示不限制
# x-request-id-limit: 0
# ============================================================
# TLS / HTTPS 设置
# ============================================================
# 启用 HTTPS 模式
# 默认 false
# secure: false
# TLS 证书文件路径
# cert: /etc/webhook/cert.pem
# TLS 私钥文件路径
# key: /etc/webhook/key.pem
# TLS 最低版本,可选 "1.0", "1.1", "1.2", "1.3"
# 默认 "1.2"
# tls-min-version: "1.2"
# TLS 加密套件,逗号分隔。留空使用 Go 默认值
# 可通过 webhook --list-cipher-suites 查看可用列表
# cipher-suites: ""
# ============================================================
# Admin 管理界面
# ============================================================
# 启用 Admin UI 和 API用于在线管理 hooks
# 默认 false
# admin: false
# Admin 界面的 URL 前缀,最终地址为 http://host:port/PREFIX/
# 默认 "admin"
# admin-path: admin
# Admin 登录使用的 TOTP 密钥Base32 编码)
# 可用 Google Authenticator 等 APP 扫码登录
# admin-totp-secret: ""
# 用于签发 Admin JWT 会话令牌的密钥
# admin-jwt-secret: ""
# Admin JWT 会话有效期,支持 Go duration 格式(如 12h、30m、720h
# 默认 "12h"
# admin-session-ttl: "12h"
# ============================================================
# Unix Socket仅 Linux/macOS
# ============================================================
# 使用 Unix socket 代替 IP:Port 监听
# 设置后 ip 和 port 选项将被忽略
# socket: /tmp/webhook.sock
# ============================================================
# 权限降级(仅 Linux/macOS
# ============================================================
# 打开监听端口后切换到指定的用户/组 ID 运行
# setuid 和 setgid 必须同时设置,不能与 socket 同时使用
# setuid: 1000
# setgid: 1000
# ============================================================
# PID 文件
# ============================================================
# 创建 PID 文件,用于进程管理
# pidfile: /var/run/webhook.pid