🤞 Sync 2026-09-27 22:16:29

This commit is contained in:
github-actions[bot] committed 2026-09-27 22:16:29 +08:00
1 parent 8ede4a8320
commit 618f51e9dc
459 files changed
+44817 -2191

No files matched your search

+54 -44
View File
@@ -1,49 +1,59 @@
# 此配置文件路径:/etc/caddy/Caddyfile
{ # 全局配置
# 启动 cgi 模块
order cgi before respond
# 启动 webdav 模块
order webdav before file_server
# 关闭 API 端口
admin off
}
# 此配置文件路径: /etc/caddy/Caddyfile
# 下面是 proxy 模式生成 Caddyfile 的样例 (通用值).
# 手动模式 (Caddyfile) 下, 本文件内容即你实际使用的配置, 可任意写 Caddy 支持的语法.
:12311 {
#启用压缩
encode gzip
#认证,账号admin 密码123456
basicauth {
admin $2a$14$RdbOHzJhf5BaapSdlYTCbe.yWY9cEZjyDpfgwStY28K/qsM1tX8tu
}
#路径 /mnt
root * /mnt
#文件服务器 端口12311
file_server browse
log {
#日志 路径/tmp/caddy/requests.log
output file /tmp/caddy/requests.log {
roll_size 1MiB
roll_local_time
roll_keep 5
roll_keep_for 120h
}
}
{
auto_https disable_redirects
aliyun_ddns {
access_key_id <ACCESS_KEY_ID>
access_key_secret <ACCESS_KEY_SECRET>
region_id cn-hangzhou
domains {
example.com @
}
versions ipv4
ip_source interface pppoe-wan
check_interval 5m
ttl 60s
}
}
:12322 {
#启用压缩
encode gzip
#认证,账号admin 密码123456
basicauth {
admin $2a$14$RdbOHzJhf5BaapSdlYTCbe.yWY9cEZjyDpfgwStY28K/qsM1tX8tu
}
example.com:8443 {
tls {
dns alidns {
access_key_id <ACCESS_KEY_ID>
access_key_secret <ACCESS_KEY_SECRET>
}
}
# webdav 路径根目录 端口12322
webdav /dav/* {
prefix /dav
root /
}
header -Server
request_body max_size 0
@health path /health
@apikey header Authorization "Bearer <API_KEY>"
handle @health {
reverse_proxy 192.168.1.x:8080 { flush_interval -1 }
}
handle @apikey {
reverse_proxy 192.168.1.x:8080 { flush_interval -1 }
}
handle {
respond 404 "Not Found" { close }
}
log {
output file /tmp/caddy/requests.log {
roll_size 1MiB
roll_local_time
roll_keep 5
roll_keep_for 120h
}
}
}
+27 -13
View File
@@ -1,14 +1,28 @@
# luci-app-caddy 默认配置
#
# 数据模型:
# config caddy -> 全局 (单条): 启用 / 路径 / 重定向开关
# config domain -> 域名 (多条, 增删): 每条 = 一个域名 + AK + DDNS 参数
# config proxy -> Host (多条, 增删): 每条 = 一段 Caddyfile 的 域名:端口 { ... }
#
# domain 示例 (在 LuCI [Domain] 页点 添加 创建):
# config domain 'd1'
# option domain_name 'example.com'
# option key_id '<AK_ID>'
# option key_secret '<AK_SECRET>'
# option ddns_on '0'
#
# proxy 示例 (在 LuCI [Host] 页点 添加 创建):
# config proxy 'host1'
# option name 'my-host'
# option enabled '1'
# option domain 'example.com'
# option port '8443'
# option tls '1'
# option target '192.168.1.x:8080'
config caddy
option 'enabled' '0'
option cmd '默认'
option 'port' '12311'
option 'webdav' '0'
option 'filezip' '0'
option 'webzip' '0'
option 'cgi' '0'
option 'api' '0'
option 'data_dir' '/mnt'
option 'file_pass' '0'
option 'log' '1'
option 'log_dir' '/tmp/caddy/requests.log'
option allow_wan '0'
option enabled '0'
option disable_redirect '1'
option bin_dir '/usr/sbin/caddy'
option log_dir '/tmp/caddy/requests.log'
+310 -161
View File
@@ -2,178 +2,315 @@
START=99
USE_PROCD=1
PROG="$(uci -q get caddy.@caddy[0].bin_dir)"
get_config() {
config_get_bool enabled $1 enabled 0
config_get cmd $1 cmd "默认"
config_get_bool webdav $1 webdav 0
config_get port $1 port 12311
config_get webdav_port $1 webdav_port 12322
config_get log $1 log 1
config_get data_dir $1 data_dir "/mnt"
config_get webdav_data_dir $1 webdav_data_dir "/mnt"
config_get log_dir $1 log_dir "/tmp/caddy/requests.log"
config_get caddy_file $1 caddy_file "/etc/caddy/Caddyfile"
config_get caddyfile $1 caddyfile
config_get file_pass $1 file_pass 0
config_get file_username $1 file_username "admin"
config_get file_password $1 file_password "123456"
config_get webdav_pass $1 webdav_pass 0
config_get webdav_username $1 webdav_username "admin"
config_get webdav_password $1 webdav_password "123456"
config_get api $1 api 0
config_get filezip $1 filezip 0
config_get webzip $1 webzip 0
config_get allow_wan $1 allow_wan 0
# ---- 全局值 (来自 @caddy[0]) ----
G_BIN="$(uci -q get caddy.@caddy[0].bin_dir)"
[ -z "$G_BIN" ] && G_BIN="/usr/sbin/caddy"
G_FILE="$(uci -q get caddy.@caddy[0].caddy_file)"
[ -z "$G_FILE" ] && G_FILE="/etc/caddy/Caddyfile"
G_LOG="$(uci -q get caddy.@caddy[0].log_dir)"
[ -z "$G_LOG" ] && G_LOG="/tmp/caddy/requests.log"
TMP="$G_FILE.new"
# 列出所有 domain section 的 UCI 路径 (支持命名+匿名)
# 返回完整路径如 caddy.d1 或 caddy.@domain[0]
list_all_domains() {
uci show caddy 2>/dev/null | grep '\.domain_name' | sed 's/\.domain_name=.*//'
}
set_firewall() {
if [ "$external_access" = "allow" ]; then
uci -q delete firewall.caddy
uci set firewall.caddy=rule
uci set firewall.caddy.name="caddy"
uci set firewall.caddy.target="ACCEPT"
uci set firewall.caddy.src="wan"
uci set firewall.caddy.proto="tcp"
uci set firewall.caddy.dest_port="$port"
[ ! -z "$webdav_port" ] && uci set firewall.caddy.dest_port="$webdav_port"
uci set firewall.caddy.enabled="1"
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
elif [ "$external_access" = "deny" ]; then
uci -q delete firewall.caddy
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
# 按 domain_name 查找匹配的 section UCI 路径
find_domain_by_name() {
local target="$1" ref dn
for ref in $(list_all_domains); do
dn="$(uci -q get "$ref".domain_name)"
[ "$dn" = "$target" ] && echo "$ref" && return 0
done
return 1
}
# 列出所有 proxy section (匿名+命名都认, 按 target 字段识别)
# 匿名 section 在 uci show 里形如 caddy.@proxy[N]
list_all_proxies() {
uci show caddy 2>/dev/null | grep '\.target=' | sed 's/\.target=.*//'
}
# ---- 生成 Caddyfile ----
# 逻辑: 全局块 (auto_https + 每个 ddns_on 的 domain 生成 aliyun_ddns) + 每个启用的 host
generate_caddyfile() {
local cf="$TMP"
local disr
local p dom prt tgt key tls_cert extra
disr="$(uci -q get caddy.@caddy[0].disable_redirect)"
{
echo "# /etc/caddy/Caddyfile"
echo "# generated by luci-app-caddy, edit via LuCI -> Network -> Caddy"
echo
echo "{"
# auto_https: 勾选才写
if [ "$disr" = "1" ]; then
echo " auto_https disable_redirects"
fi
# CA 固定 Let's Encrypt, 避开 Caddy 默认可能跳 ZeroSSL
echo " acme_ca https://acme-v02.api.letsencrypt.org/directory"
# --- DDNS: 遍历 domain, ddns_on=1 的生成 aliyun_ddns 块 ---
for d in $(list_all_domains); do
local don dkid dksec dname drec diface dint dttl
don="$(uci -q get "$d".ddns_on)"
[ "$don" != "1" ] && continue
dname="$(uci -q get "$d".domain_name)"
dkid="$(uci -q get "$d".key_id)"
dksec="$(uci -q get "$d".key_secret)"
drec="$(uci -q get "$d".ddns_records)"
diface="$(uci -q get "$d".ddns_wan_iface)"
dint="$(uci -q get "$d".ddns_check_interval)"
dttl="$(uci -q get "$d".ddns_ttl)"
[ -z "$dname" ] && continue
[ -z "$dkid" ] && continue
[ -z "$dksec" ] && continue
[ -z "$dint" ] && dint="5m"
[ -z "$dttl" ] && dttl="60s"
echo
echo " # DDNS: $dname (@ = 主域 A 记录, * = 泛解析 *.domain 独立记录, 二者互不串用)"
echo " aliyun_ddns {"
echo " access_key_id $dkid"
echo " access_key_secret $dksec"
echo
echo " domains {"
echo " $dname @"
printf '%s\n' "$drec" | while IFS= read -r r; do
[ -z "$r" ] && continue
echo " $dname $r"
done
echo " }"
echo
echo " versions ipv4"
case "$diface" in
public|"")
: # 走插件默认 simple_http (api.ipify.org / icanhazip.com / ifconfig.me / ident.me)
;;
*)
echo " ip_source interface $diface"
;;
esac
echo " check_interval $dint"
echo " ttl $dttl"
echo " }"
done
echo "}"
# ---- Host: 每个启用的 proxy (匿名/命名都认) ----
for p in $(list_all_proxies); do
local pen
pen="$(uci -q get "$p".enabled)"
[ "$pen" = "0" ] && continue
# 一次性迁移: 下拉现在只有 auto/off, 任何旧值 (tls Flag / 静态证书路径) 归到这两个
local tlsval
tlsval="$(uci -q get "$p".tls_cert)"
case "$tlsval" in
auto|off|"")
if [ -z "$tlsval" ]; then
# 从未设过: 按旧版 tls Flag 定 (1=auto, 其余 off)
if [ "$(uci -q get "$p".tls)" = "1" ]; then
tlsval=auto
else
tlsval=off
fi
uci -q set "$p".tls_cert="$tlsval"
uci commit caddy
fi
;;
*) # 旧值 (静态证书路径等) 一律归 auto, 写回 UCI 一次性归一
uci -q set "$p".tls_cert=auto
uci commit caddy
tlsval=auto
;;
esac
dom="$(uci -q get "$p".domain)"
prt="$(uci -q get "$p".port)"
tgt="$(uci -q get "$p".target)"
key="$(uci -q get "$p".api_key)"
extra="$(uci -q get "$p".extra)"
[ -z "$prt" ] && prt="443"
[ -z "$dom" ] && continue
[ -z "$tgt" ] && continue
# 取对应 Domain 的 AK (Host 域名剥掉 *. 前缀再查 Domain 页)
# 注意: 必须用 \*.*) 转义前导星号, 否则 example.net 这种含点的裸域会误中 *.*
local dmatch dkid dksec dlookup
dlookup="$dom"
case "$dlookup" in
\*.*) dlookup="${dlookup#\*.}" ;;
esac
dmatch="$(find_domain_by_name "$dlookup")"
dkid=""
dksec=""
if [ -n "$dmatch" ]; then
dkid="$(uci -q get "$dmatch".key_id)"
dksec="$(uci -q get "$dmatch".key_secret)"
fi
# 监听地址: 完全由表单的 domain+port 决定, 代码不替用户加地址
# 选了 *.x 时, 同一行带上裸域 x (官方"一行多名字"模式):
# certmagic 逐名字签发 → 裸域、通配符各一张证书(各一条 order)
# ≡ acme.sh -d x --dns + -d '*.x' --dns, 效果等价: 裸域+全部子域都覆盖
local addr="$dom:$prt"
case "$dom" in
\*.*) addr="$dom:$prt ${dom#\*.}:$prt" ;;
esac
echo
echo "$addr {"
# TLS: 只有 auto / off 两个值 (下拉已砍掉静态证书选项)
# auto + Domain 页有 AK: 一律 DNS-01 (裸域/通配符都不依赖 80/443 入站, 适合 NAT 家庭宽带)
# auto + 无 AK: 回退官方 HTTP-01 (一行 tls 都不写, Caddy 自动处理)
# 用上面迁移后的 $tlsval, 不重读 UCI (迁移刚写回时本轮即生效)
case "$tlsval" in
auto|"")
if [ -n "$dkid" ] && [ -n "$dksec" ]; then
echo " tls {"
echo " dns alidns {"
echo " access_key_id $dkid"
echo " access_key_secret $dksec"
echo " }"
echo " }"
echo
fi
;;
off)
: # 纯 HTTP, 不写 tls 块
;;
esac
echo " header -Server"
echo
echo " request_body max_size 0"
if [ -n "$key" ]; then
echo
echo " # Caddy 层 API key 校验: 除 @apikey 外一律 404 (/health 也需带 key)"
echo " @apikey header Authorization \"Bearer $key\""
echo
echo " handle @apikey {"
echo " reverse_proxy $tgt { flush_interval -1 }"
echo " }"
echo
echo " handle {"
# 无 key 的兜底: 空 body 的 404 (不回 "Not Found" 文本, 不泄露身份)
# 注意: respond 带 body 文本 + 尾部 { close } 子块时 Caddyfile 参数错位
# (404→body, 文本→status), 运行时 Atoi 炸 500. 故 body 留空.
echo " respond 404 { close }"
echo " }"
else
echo
echo " reverse_proxy $tgt {"
echo " flush_interval -1"
echo " }"
fi
# 自定义指令 (原样插入)
if [ -n "$extra" ]; then
echo
printf '%s\n' "$extra" | while IFS= read -r line; do
echo " $line"
done
fi
# 请求日志: 勾选 log_on 才生成, 关 = 不写日志, 省资源
local lon
lon="$(uci -q get caddy.@caddy[0].log_on)"
if [ "$lon" != "0" ]; then
echo
echo " # 请求日志"
echo " log {"
echo " output file $G_LOG {"
echo " roll_size 1MiB"
echo " roll_local_time"
echo " roll_keep 5"
echo " roll_keep_for 120h"
echo " }"
echo " }"
fi
echo "}"
done
} > "$cf"
mv -f "$cf" "$G_FILE"
# DDNS/AK secret 明文在文件里, 收紧权限只让 root 读写 (LuCI CGI 同以 root 运行, 不受影响)
chmod 600 "$G_FILE"
# 一次性迁移: wildcard 旧字段. 通配符选择已挪到 Host 页下拉, Domain 页不再显示该字段,
# 而 CBI 保存只写页面已定义字段、不删未定义键 → 旧 wildcard 键会永久残留 UCI. 有就删.
local wcwipe
wcwipe=0
for d in $(list_all_domains); do
case "$(uci -q get "$d".wildcard)" in
""|auto|off) : ;;
*) uci -q delete "$d".wildcard; wcwipe=1 ;;
esac
done
[ "$wcwipe" = "1" ] && uci commit caddy
# 清 UCI 冻结值: [配置] 页 TextValue 点过「保存」后会把当时内容冻进 UCI,
# 之后页面优先显示 UCI 而不再读磁盘 (表现: 这里改了但配置页看到的还是旧的).
# 每次重新生成后把键删掉, 页面即回落 o.default(读磁盘真值).
# 只在键存在时才 commit (commit 会动 /etc/config/caddy 触发 procd reload,
# 避免无谓的额外重载: 键存在→删→commit→本轮 reload 重生成时键已不在, 不会死循环)
if uci -q get caddy.@caddy[0].caddyfile >/dev/null 2>&1; then
uci -q delete caddy.@caddy[0].caddyfile
uci commit caddy
fi
}
check_prog() {
if [ ! -f "$G_BIN" ]; then
uci -q set caddy.@caddy[0].enabled=0
uci commit caddy
echo "caddy: $G_BIN 未找到" >>"$G_LOG" 2>&1
exit 1
else
chmod +x "$G_BIN"
if [ "$($G_BIN -h 2>&1 | wc -l)" -lt 3 ]; then
uci -q set caddy.@caddy[0].enabled=0
uci commit caddy
echo "caddy: 程序不完整或架构不匹配" >>"$G_LOG" 2>&1
exit 1
fi
fi
}
start_service() {
config_load caddy
config_foreach get_config caddy
[ $enabled -ne 1 ] && return 1
log_path="$(dirname $log_dir)"
mkdir -p $log_path
if [ ! -f "$PROG" ] ;then
uci -q set caddy.@caddy[0].enabled=0
uci commit caddy
echo "$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X) : $PROG 未找到,无法启动!请确认已上传程序" >>$log_dir 2>&1
exit 1
else
chmod +x $PROG
if [ $(($($PROG -h | wc -l))) -lt 3 ] ;then
uci -q set caddy.@caddy[0].enabled=0
uci commit caddy
echo "$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X) : $PROG 程序不完整或CPU架构不支持的程序,无法启动!" >>$log_dir 2>&1
exit 1
fi
fi
if [ "$cmd" = "默认" ] ;then
mkdir -p $data_dir
if [ "$log" -eq 1 ] ;then
logs="log {
output file $log_dir {
roll_size 1MiB
roll_local_time
roll_keep 5
roll_keep_for 120h
}
}"
fi
if [ "$file_pass" -eq 1 ] ;then
filepassword="$($PROG hash-password --plaintext $file_password)"
filebasicauth="basicauth {
$file_username $filepassword
}"
fi
if [ "$webdav" -eq 1 ] ;then
mkdir -p $webdav_data_dir
if [ "$webdav_pass" -eq 1 ] ;then
password="$($PROG hash-password --plaintext $webdav_password)"
basicauth="basicauth {
$webdav_username $password
}"
fi
webdavs=":$webdav_port {
$basicauth
webdav * {
prefix /dav
root $webdav_data_dir
}
}"
orderweb="order webdav before file_server # 启动 webdav 模块"
fi
if [ "$filezip" -eq 1 ] ;then
filezips="encode gzip"
fi
if [ "$webzip" -eq 1 ] ;then
webzips="encode gzip"
fi
if [ "$api" != "1" ] ;then
apis="admin off # 关闭 API 端口"
fi
if [ "$allow_wan" -eq "1" ]; then
external_access="allow"
else
external_access="deny"
fi
if [ "$webdav" -eq 1 ] || [ "$api" != "1" ] ;then
order="{ # 全局配置
$orderweb
$apis
} "
fi
set_firewall
cat > $caddy_file <<EOF
# 此配置文件路径:/etc/caddy/Caddyfile
local enabled
enabled="$(uci -q get caddy.@caddy[0].enabled)"
[ -z "$enabled" ] && enabled=0
[ "$enabled" != "1" ] && return 1
$order
mkdir -p "$(dirname "$G_LOG")"
check_prog
:$port {
$filezips
$filebasicauth
root * $data_dir
file_server browse
$logs
}
generate_caddyfile
$webdavs
EOF
"$G_BIN" fmt --overwrite "$G_FILE" 2>/dev/null || true
uci -q set caddy.@caddy[0].caddyfile="$(cat $caddy_file)"
uci commit caddy
else
caddyf="$(uci -q get caddy.@caddy[0].caddyfile)"
logpath="$(echo $caddyf | grep "output file" | awk -F 'output file' '{print $2}' | tr -d '{ ')"
[ ! -z "$logpath" ] && uci -q set caddy.@caddy[0].log_dir=$logpath && uci commit caddy
if [ ! -z "$caddyf" ] ;then
echo "$caddyf" >$caddy_file 2>&1
else
uci -q set caddy.@caddy[0].enabled=0
uci commit caddy
echo "$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X) : $caddy_file 配置文件Caddyfile为空,无法启动!" >>$log_dir 2>&1
exit 1
fi
fi
$PROG fmt --overwrite $caddy_file
procd_open_instance caddy
procd_set_param command $PROG
procd_append_param command run
procd_append_param command --config $caddy_file
procd_append_param command --adapter caddyfile
procd_set_param stdout 0
procd_set_param stderr 0
procd_set_param command "$G_BIN" "run" "--config" "$G_FILE" "--adapter" caddyfile
procd_set_param pidfile /var/run/caddy.pid
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance caddy
echo `date +%s` > /tmp/caddy_time
date +%s > /tmp/caddy_time
}
service_triggers() {
@@ -181,14 +318,26 @@ service_triggers() {
}
stop_service() {
external_access="deny"
set_firewall
rm -rf /tmp/caddy.tag /tmp/caddynew.tag
rm -rf $(uci -q get caddy.@caddy[0].log_dir)
procd_kill caddy 2>/dev/null
rm -f /tmp/caddynew.tag
rm -f "$G_LOG" 2>/dev/null
rm -f /var/run/caddy.pid
return 0
}
reload_service() {
# 覆盖 rc.common 的 restart(): 官方零停机热重载 (caddy reload, 等价 POST /load)
# 有运行实例 -> 热重载, 旧连接不断; 无实例 -> 完整 stop/start
restart() {
generate_caddyfile
"$G_BIN" fmt --overwrite "$G_FILE" 2>/dev/null || true
if pidof caddy >/dev/null 2>&1; then
"$G_BIN" reload --config "$G_FILE" --adapter caddyfile >/dev/null 2>&1 && return 0
fi
stop
sleep 1
start
}
# reload: procd 模式 rc.common 调 reload_service, 委托给热重载
reload_service() {
restart
}