Running Caddy on OpenWrt with Access to LuCI
This article describes how to run Caddy on OpenWrt while keep the access to LuCI.
tl;dr
To run LuCI on Caddy, prepare a Caddyfile
{
order file_server last
}
:80 {
cgi /cgi-bin/luci* /www/cgi-bin/luci { script_name /cgi-bin/luci }
cgi /ubus* ubus.sh { script_name /ubus }
file_server /luci-static* { root /www }
redir / /cgi-bin/luci
}
JUN 2023 UPDATED: The original Caddyfile
has some problems and the newly updated one is shown below, and with changelogs:
- Some pages require extra CGI programs at
/cgi-bin/cgi-*
, which the old one doesn’t include - Adapt to latest Caddy version
- Support self-signed TLS (by uncommenting those lines)
{
order cgi before respond
order file_server last
# skip_install_trust
# auto_https disable_redirects
# default_sni openwrt.local
}
(luci) {
root * /www
route /cgi-bin* {
@exists {
file cgi-bin/{path.1} =404
}
handle @exists {
uri strip_prefix {file_match.relative}
cgi * /www/{file_match.relative} {
script_name {file_match.relative}
}
}
}
cgi /ubus* ubus.sh {
script_name /ubus
}
file_server
redir / /cgi-bin/luci
}
http:// {
import luci
}
# https:// {
# tls internal {
# on_demand
# }
# import luci
# }
Download ubus.sh
from yurt-page/cgi-ubus and put it together with Caddyfile.
To fetch Caddy executable with CGI support for a router:
-
either download at https://caddyserver.com/download with plugin
aksdb/caddy-cgi/v2
chosenThis should work for most architectures. But there are issues you may encounter with MIPS, which are probably related to FPU, so you should build it with
xcaddy
. -
or get
xcaddy
by runninggo install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
clone the plugin
git clone https://github.com/aksdb/caddy-cgi.git
a folder called
caddy-cgi
will appear in current working directory, then runGOOS=linux GOARCH=YOUR_ARCH xcaddy build \ --with github.com/jung-kurt/caddy-cgi=./caddy-cgi
to build
caddy
with CGI support for your router.For example:
GOOS=linux GOARCH=mipsle GOMIPS=softfloat xcaddy build \ --with github.com/jung-kurt/caddy-cgi=./caddy-cgi
Intro
Caddy is a powerful HTTP server with lots of handy & easily-configurable modules
OpenWRT is a popular Linux distro mainly designed for network devices (home routers) & embedded devices. It has a built-in HTTP server called uhttpd
. It is also “powerful” but the prefix u
(i.e. μ micro) suggests that it lacks some features. For example, it would not pass some headers like Authorization
to CGI backends. OpenWRT runs LuCI, a web management system of routers, on uhttpd
.
I developed some web applications running on my router with Lua WSAPI CGI. What I want is that these applications & LuCI share the port 80 for HTTP. But there cannot be two or more applications binding to the same TCP port (strictly the same IP address + port). So a natural way to do this is to bind uhttpd
to another loopback port, and add a reverse proxy in Caddy.
I removed HTTPS listeners because HTTPS will be managed by Caddy, and change the wildcard binding to loopback:8000
,
--- /etc/config/uhttpd
+++ /etc/config/uhttpd
@@ -1,5 +1,3 @@
config uhttpd 'main'
- list listen_http '0.0.0.0:80'
- list listen_http '[::]:80'
- list listen_https '0.0.0.0:443'
- list listen_https '[::]:443'
+ list listen_http '127.0.0.1:8000'
+ list listen_http '[::1]:8000'
also prepared a Caddyfile
to run Caddy:
:80 { reverse_proxy 127.0.0.1:8000 }
The work above was expected to work until I tested it.
LuCI fetches device information (wireless list, interfaces, DHCP leases etc.) through a ubus
JSONRPC interface.
This is the details of RPC response:
It is evident that the response is aborted thus truncated for some reasons. I found no solutions for it after trying to adjust dial_timeout
, flush_interval
etc. I could just treat it as a feature. :(
Solution
ubus
JSONRPC interface is also implemented by part of LuCI. So it might be possible to make a new ubus
RPC in Shell or any other language that is able to interact with CGI. Therefore, all things could be done in Caddy. I found a post and got its repo yurt-page/cgi-ubus, with the following Caddyfile:
{
order file_server last
}
:80 {
cgi /cgi-bin/luci* /www/cgi-bin/luci { script_name /cgi-bin/luci }
cgi /ubus* ubus.sh { script_name /ubus }
file_server /luci-static* { root /www }
redir / /cgi-bin/luci
}
However the script did not support multiple JSONRPC requests at once. (i.e. the body is an array of several JSONRPC requests)
After patching ubus.sh
, it comes to work, with a pull request.