git: eb8f323512ce - main - www/bunkerweb: Make the scheduler and the API work together
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 27 Sep 2026 07:20:14 UTC
The branch main has been updated by joneum:
URL: https://cgit.FreeBSD.org/ports/commit/?id=eb8f323512cef6c0ea7022e9e0ef3629261c6b3a
commit eb8f323512cef6c0ea7022e9e0ef3629261c6b3a
Author: Jochen Neumeister <joneum@FreeBSD.org>
AuthorDate: 2026-09-27 06:41:38 +0000
Commit: Jochen Neumeister <joneum@FreeBSD.org>
CommitDate: 2026-09-27 07:20:00 +0000
www/bunkerweb: Make the scheduler and the API work together
The scheduler registered the instance with HTTP_PORT instead of
API_HTTP_PORT, so it never reached it and ran no jobs at all.
Run it as the worker user and share the directories with the API,
which replaces those files on a push. Point the remaining Linux paths
in the API and the config saver at their FreeBSD locations, teach the
reload path about the openresty binary, and let the worker reload
through the rc script, the fallback upstream already uses.
Sponsored by: Netzkommune GmbH
---
www/bunkerweb/Makefile | 8 ++-
www/bunkerweb/files/bunkerweb.in | 3 +
www/bunkerweb/files/bunkerweb.sudoers | 4 ++
www/bunkerweb/files/bunkerweb_scheduler.in | 21 +++++--
.../files/patch-src_bw_lua_bunkerweb_api.lua | 72 ++++++++++++++++++++++
.../files/patch-src_common_gen_save__config.py | 23 +++----
www/bunkerweb/files/patch-src_scheduler_main.py | 27 +++++---
www/bunkerweb/pkg-plist | 9 +--
8 files changed, 134 insertions(+), 33 deletions(-)
diff --git a/www/bunkerweb/Makefile b/www/bunkerweb/Makefile
index d2dead141fc1..9f42aa167a8c 100644
--- a/www/bunkerweb/Makefile
+++ b/www/bunkerweb/Makefile
@@ -1,6 +1,6 @@
PORTNAME= bunkerweb
DISTVERSION= 1.6.15
-PORTREVISION= 1
+PORTREVISION= 2
CATEGORIES= www security
MAINTAINER= joneum@FreeBSD.org
@@ -17,6 +17,7 @@ GH_PROJECT= bunkerweb
GH_TAGNAME= v${DISTVERSION}
RUN_DEPENDS= openresty:www/openresty \
+ sudo:security/sudo \
${PYTHON_PKGNAMEPREFIX}Jinja2>0:devel/py-Jinja2@${PY_FLAVOR} \
${PYTHON_PKGNAMEPREFIX}pydantic-settings>0:devel/py-pydantic-settings@${PY_FLAVOR} \
${PYTHON_PKGNAMEPREFIX}schedule>0:devel/py-schedule@${PY_FLAVOR} \
@@ -67,6 +68,8 @@ DATADIR= ${PREFIX}/share/${PORTNAME}
post-patch:
${FIND} ${WRKSRC} -name "*.orig" -delete
+ ${REINPLACE_CMD} -e 's|python3 ${DATADIR}/common/utils/|${PYTHON_CMD} ${DATADIR}/common/utils/|' \
+ ${WRKSRC}/src/bw/lua/bunkerweb/api.lua
do-install:
${MKDIR} ${STAGEDIR}${DATADIR}
@@ -90,5 +93,8 @@ post-install:
${STAGEDIR}${DATADIR}/common/db/
${INSTALL_DATA} ${FILESDIR}/api.yml.sample ${STAGEDIR}${ETCDIR}/
+ ${MKDIR} ${STAGEDIR}${PREFIX}/etc/sudoers.d
+ ${INSTALL_DATA} -m 0440 ${FILESDIR}/bunkerweb.sudoers \
+ ${STAGEDIR}${PREFIX}/etc/sudoers.d/bunkerweb
.include <bsd.port.mk>
diff --git a/www/bunkerweb/files/bunkerweb.in b/www/bunkerweb/files/bunkerweb.in
index a06052a575f5..2bd5c84f31f4 100644
--- a/www/bunkerweb/files/bunkerweb.in
+++ b/www/bunkerweb/files/bunkerweb.in
@@ -23,12 +23,15 @@ command_args="-e ${bunkerweb_logdir}/error.log -p ${bunkerweb_prefix} -c ${bunke
start_precmd="${name}_prestart"
stop_cmd="${name}_stop"
reload_cmd="${name}_reload"
+extra_commands="reload"
bunkerweb_prestart()
{
install -d -m 0755 "${bunkerweb_prefix}"
install -d -m 0755 "${bunkerweb_logdir}"
install -d -m 0755 /var/run/bunkerweb
+ install -d -m 0750 -o www -g www /var/tmp/bunkerweb
+
if [ ! -f "${bunkerweb_conf}" ]; then
warn "${bunkerweb_conf} does not exist yet. Start bunkerweb_scheduler and wait for it to generate the configuration."
return 1
diff --git a/www/bunkerweb/files/bunkerweb.sudoers b/www/bunkerweb/files/bunkerweb.sudoers
new file mode 100644
index 000000000000..fb572540a0b0
--- /dev/null
+++ b/www/bunkerweb/files/bunkerweb.sudoers
@@ -0,0 +1,4 @@
+# The API reloads the instance from an nginx worker, which runs as www,
+# while the master runs as root. Upstream falls back to the rc script
+# through sudo for exactly this case.
+www ALL=(root) NOPASSWD: /usr/sbin/service bunkerweb reload
diff --git a/www/bunkerweb/files/bunkerweb_scheduler.in b/www/bunkerweb/files/bunkerweb_scheduler.in
index ef171176c841..d3b9b90071a2 100644
--- a/www/bunkerweb/files/bunkerweb_scheduler.in
+++ b/www/bunkerweb/files/bunkerweb_scheduler.in
@@ -12,14 +12,15 @@ rcvar="bunkerweb_scheduler_enable"
load_rc_config "$name"
: ${bunkerweb_scheduler_enable:="NO"}
+: ${bunkerweb_scheduler_logfile:="/var/log/bunkerweb/scheduler.log"}
: ${bunkerweb_scheduler_database_uri:="sqlite:////var/lib/bunkerweb/db.sqlite3"}
_bunkerweb_pythonpath="/usr/local/share/bunkerweb/common/gen:/usr/local/share/bunkerweb/common/utils:/usr/local/share/bunkerweb/common/db:/usr/local/share/bunkerweb/common/api:/usr/local/share/bunkerweb/deps/python"
-pidfile="/var/run/${name}.pid"
+pidfile="/var/run/bunkerweb/${name}.pid"
procname="%%PREFIX%%/bin/%%PYTHON_VERSION%%"
command="/usr/sbin/daemon"
-command_args="-f -p ${pidfile} /usr/bin/env PYTHONPATH=${_bunkerweb_pythonpath} %%PREFIX%%/bin/%%PYTHON_VERSION%% /usr/local/share/bunkerweb/scheduler/main.py"
+command_args="-f -u www -p ${pidfile} -o ${bunkerweb_scheduler_logfile} /usr/bin/env PYTHONPATH=${_bunkerweb_pythonpath} %%PREFIX%%/bin/%%PYTHON_VERSION%% /usr/local/share/bunkerweb/scheduler/main.py"
bunkerweb_scheduler_precmd()
{
@@ -45,16 +46,24 @@ bunkerweb_scheduler_precmd()
-out /usr/local/share/bunkerweb/misc/root-ca.pem >/dev/null 2>&1
fi
- chown -R bunkerweb:bunkerweb /var/cache/bunkerweb
+ chown -R www:www /var/cache/bunkerweb
chmod 600 /var/cache/bunkerweb/misc/default-server-cert.key
chmod 644 /var/cache/bunkerweb/misc/default-server-cert.pem
- chown root:bunkerweb /usr/local/share/bunkerweb/misc/root-ca.key /usr/local/share/bunkerweb/misc/root-ca.pem
+ chown root:www /usr/local/share/bunkerweb/misc/root-ca.key /usr/local/share/bunkerweb/misc/root-ca.pem
chmod 640 /usr/local/share/bunkerweb/misc/root-ca.key
chmod 644 /usr/local/share/bunkerweb/misc/root-ca.pem
- chown bunkerweb:bunkerweb /var/run/bunkerweb
- chown -R bunkerweb:bunkerweb /var/log/bunkerweb
+ chown www:www /var/run/bunkerweb
+ chown -R www:www /var/log/bunkerweb
+
+ # The scheduler runs as the worker user: it writes the configuration that
+ # the nginx worker replaces when the scheduler pushes it back.
+ for _d in /var/lib/bunkerweb /var/tmp/bunkerweb /usr/local/etc/nginx /usr/local/etc/bunkerweb/configs \
+ /usr/local/etc/bunkerweb/plugins /usr/local/etc/bunkerweb/pro/plugins; do
+ install -d -m 0750 -o www -g www "${_d}"
+ chown -R www:www "${_d}"
+ done
# Upstream migrates the database from its systemd wrapper, which
# FreeBSD does not use, so do it here before the scheduler starts.
diff --git a/www/bunkerweb/files/patch-src_bw_lua_bunkerweb_api.lua b/www/bunkerweb/files/patch-src_bw_lua_bunkerweb_api.lua
new file mode 100644
index 000000000000..0669dd25f00a
--- /dev/null
+++ b/www/bunkerweb/files/patch-src_bw_lua_bunkerweb_api.lua
@@ -0,0 +1,72 @@
+--- src/bw/lua/bunkerweb/api.lua.orig 2026-09-21 08:05:39 UTC
++++ src/bw/lua/bunkerweb/api.lua
+@@ -65,7 +65,7 @@
+ end
+
+ local function get_nginx_bin()
+- local candidates = { "/usr/sbin/nginx", "/usr/local/sbin/nginx", "/usr/bin/nginx", "/usr/local/bin/nginx" }
++ local candidates = { "/usr/local/bin/openresty", "/usr/sbin/nginx", "/usr/local/sbin/nginx", "/usr/bin/nginx", "/usr/local/bin/nginx" }
+ for _, candidate in ipairs(candidates) do
+ if file_exists(candidate) then
+ return candidate
+@@ -81,7 +81,7 @@
+ return candidate
+ end
+ end
+- return "/etc/nginx/nginx.conf"
++ return "/usr/local/etc/nginx/nginx.conf"
+ end
+
+ api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
+@@ -249,7 +249,7 @@
+
+ local function check_audit_storage(variables_path)
+ -- Only called with our fixed config path or the internally generated staging path.
+- local handle = io.popen("python3 /usr/share/bunkerweb/utils/modsecurity_audit.py '" .. variables_path .. "' 2>&1")
++ local handle = io.popen("python3 /usr/local/share/bunkerweb/common/utils/modsecurity_audit.py '" .. variables_path .. "' 2>&1")
+ if not handle then
+ return false, "cannot run ModSecurity audit preflight"
+ end
+@@ -261,7 +261,7 @@
+ -- The body returns the response triple instead of sending it, so the wrapper has one
+ -- place to release the swap lock whichever way the reload ends.
+ local function reload_locked(test_arg)
+- local valid, validation_error = check_audit_storage("/etc/nginx/variables.env")
++ local valid, validation_error = check_audit_storage("/usr/local/etc/nginx/variables.env")
+ if not valid then
+ return HTTP_INTERNAL_SERVER_ERROR, "error", validation_error
+ end
+@@ -351,19 +351,19 @@
+ -- second scheduler or the UI reaches the same instance on its own.
+ local request_id = tostring(ngx.worker.pid()) .. "." .. tostring(ngx.var.connection)
+ local tmp = "/var/tmp/bunkerweb/api_" .. self.ctx.bw.uri:sub(2) .. "." .. request_id .. ".tar.gz"
+- local destination = "/usr/share/bunkerweb/" .. self.ctx.bw.uri:sub(2)
++ local destination = "/usr/local/share/bunkerweb/" .. self.ctx.bw.uri:sub(2)
+ if self.ctx.bw.uri == "/confs" then
+- destination = "/etc/nginx"
++ destination = "/usr/local/etc/nginx"
+ elseif self.ctx.bw.uri == "/data" then
+ destination = "/data"
+ elseif self.ctx.bw.uri == "/cache" then
+ destination = "/var/cache/bunkerweb"
+ elseif self.ctx.bw.uri == "/custom_configs" then
+- destination = "/etc/bunkerweb/configs"
++ destination = "/usr/local/etc/bunkerweb/configs"
+ elseif self.ctx.bw.uri == "/plugins" then
+- destination = "/etc/bunkerweb/plugins"
++ destination = "/usr/local/etc/bunkerweb/plugins"
+ elseif self.ctx.bw.uri == "/pro_plugins" then
+- destination = "/etc/bunkerweb/pro/plugins"
++ destination = "/usr/local/etc/bunkerweb/pro/plugins"
+ end
+ local form, err = upload:new(4096)
+ if not form then
+@@ -431,7 +431,7 @@
+ return self:response(HTTP_INTERNAL_SERVER_ERROR, "error", "cannot extract archive")
+ end
+
+- if destination == "/etc/nginx" then
++ if destination == "/usr/local/etc/nginx" then
+ local ran, valid, validation_error = pcall(check_audit_storage, staging .. "/variables.env")
+ if not ran or not valid then
+ execute("rm -rf '" .. staging .. "'")
diff --git a/www/bunkerweb/files/patch-src_common_gen_save__config.py b/www/bunkerweb/files/patch-src_common_gen_save__config.py
index 6ee95f64d556..47d1f1312199 100644
--- a/www/bunkerweb/files/patch-src_common_gen_save__config.py
+++ b/www/bunkerweb/files/patch-src_common_gen_save__config.py
@@ -1,20 +1,17 @@
--- src/common/gen/save_config.py.orig 2026-05-28 20:12:40 UTC
+++ src/common/gen/save_config.py
-@@ -31,7 +31,7 @@ if __name__ == "__main__":
+@@ -32,10 +32,10 @@
try:
# Parse arguments
parser = ArgumentParser(description="BunkerWeb config saver")
- parser.add_argument("--settings", default=join(sep, "usr", "share", "bunkerweb", "settings.json"), type=str, help="file containing the main settings")
+- parser.add_argument("--core", default=join(sep, "usr", "share", "bunkerweb", "core"), type=str, help="directory containing the core plugins")
+- parser.add_argument("--plugins", default=join(sep, "etc", "bunkerweb", "plugins"), type=str, help="directory containing the external plugins")
+- parser.add_argument("--pro-plugins", default=join(sep, "etc", "bunkerweb", "pro", "plugins"), type=str, help="directory containing the pro plugins")
+ parser.add_argument("--settings", default=join(sep, "usr", "local", "share", "bunkerweb", "common", "settings.json"), type=str, help="file containing the main settings")
- parser.add_argument("--core", default=join(sep, "usr", "share", "bunkerweb", "core"), type=str, help="directory containing the core plugins")
- parser.add_argument("--plugins", default=join(sep, "etc", "bunkerweb", "plugins"), type=str, help="directory containing the external plugins")
- parser.add_argument("--pro-plugins", default=join(sep, "etc", "bunkerweb", "pro", "plugins"), type=str, help="directory containing the pro plugins")
-@@ -177,7 +177,7 @@ if __name__ == "__main__":
- # Use API builder to compute scheme and port from URL or parts
- endpoint = API.build_endpoint(
- bw_instance,
-- port=settings.get("API_HTTP_PORT"),
-+ port=settings.get("HTTP_PORT", "80"),
- listen_https=(settings.get("API_LISTEN_HTTPS", "no") or "no").lower() == "yes",
- https_port=settings.get("API_HTTPS_PORT"),
- )
++ parser.add_argument("--core", default=join(sep, "usr", "local", "share", "bunkerweb", "common", "core"), type=str, help="directory containing the core plugins")
++ parser.add_argument("--plugins", default=join(sep, "usr", "local", "etc", "bunkerweb", "plugins"), type=str, help="directory containing the external plugins")
++ parser.add_argument("--pro-plugins", default=join(sep, "usr", "local", "etc", "bunkerweb", "pro", "plugins"), type=str, help="directory containing the pro plugins")
+ parser.add_argument("--variables", type=str, help="path to the file containing environment variables")
+ parser.add_argument("--init", action="store_true", help="Only initialize the database")
+ parser.add_argument("--method", default="scheduler", type=str, help="The method that is used to save the config")
diff --git a/www/bunkerweb/files/patch-src_scheduler_main.py b/www/bunkerweb/files/patch-src_scheduler_main.py
index 8286431f1c0e..6e8e5f980390 100644
--- a/www/bunkerweb/files/patch-src_scheduler_main.py
+++ b/www/bunkerweb/files/patch-src_scheduler_main.py
@@ -1,15 +1,15 @@
--- src/scheduler/main.py.orig 2026-05-28 20:12:40 UTC
+++ src/scheduler/main.py
-@@ -23,7 +23,7 @@ from typing import Any, Dict, List, Literal, Optional,
+@@ -23,7 +23,7 @@
from traceback import format_exc
- from typing import Any, Dict, List, Literal, Optional, Set, Union, cast
+ from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, cast
-BUNKERWEB_PATH = Path(sep, "usr", "share", "bunkerweb")
+BUNKERWEB_PATH = Path(sep, "usr", "local", "share", "bunkerweb")
- for deps_path in [BUNKERWEB_PATH.joinpath(*paths).as_posix() for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
+ for deps_path in [BUNKERWEB_PATH.joinpath(*paths).as_posix() for paths in (("deps", "python"), ("utils",), ("api",), ("db",), ("gen",))]:
if deps_path not in sys_path:
-@@ -50,7 +50,7 @@ CACHE_PATH.mkdir(parents=True, exist_ok=True)
+@@ -75,7 +75,7 @@
CACHE_PATH = Path(sep, "var", "cache", "bunkerweb")
CACHE_PATH.mkdir(parents=True, exist_ok=True)
@@ -18,7 +18,7 @@
CUSTOM_CONFIGS_PATH.mkdir(parents=True, exist_ok=True)
CUSTOM_CONFIGS_DIRS = (
"http",
-@@ -67,13 +67,13 @@ for custom_config_dir in CUSTOM_CONFIGS_DIRS:
+@@ -92,13 +92,13 @@
for custom_config_dir in CUSTOM_CONFIGS_DIRS:
CUSTOM_CONFIGS_PATH.joinpath(custom_config_dir).mkdir(parents=True, exist_ok=True)
@@ -35,7 +35,7 @@
PRO_PLUGINS_PATH.mkdir(parents=True, exist_ok=True)
TMP_PATH = Path(sep, "var", "tmp", "bunkerweb")
-@@ -181,9 +181,15 @@ def handle_reload(signum, frame):
+@@ -257,9 +257,15 @@
proc = subprocess_run(
[
@@ -53,7 +53,16 @@
"--variables",
join(sep, "etc", "bunkerweb", "variables.env"),
],
-@@ -522,11 +528,17 @@ def generate_configs(logger: Logger = LOGGER) -> bool:
+@@ -544,7 +550,7 @@
+ global PLUGIN_VALIDATOR
+
+ if PLUGIN_VALIDATOR is None:
+- PLUGIN_VALIDATOR = Configurator(BUNKERWEB_PATH / "settings.json", BUNKERWEB_PATH / "core", [], [], {}, LOGGER)
++ PLUGIN_VALIDATOR = Configurator(BUNKERWEB_PATH / "common" / "settings.json", BUNKERWEB_PATH / "common" / "core", [], [], {}, LOGGER)
+ return PLUGIN_VALIDATOR
+
+
+@@ -719,11 +725,17 @@
# run the generator
proc = subprocess_run(
[
@@ -74,7 +83,7 @@
"--output",
CONFIG_PATH.as_posix(),
],
-@@ -781,9 +793,15 @@ if __name__ == "__main__":
+@@ -1169,9 +1181,15 @@
# run the config saver
proc = subprocess_run(
[
@@ -92,7 +101,7 @@
"--first-run",
"--variables",
env_file_path.as_posix(),
-@@ -858,9 +876,15 @@ if __name__ == "__main__":
+@@ -1233,9 +1251,15 @@
proc = subprocess_run(
[
diff --git a/www/bunkerweb/pkg-plist b/www/bunkerweb/pkg-plist
index d0fea5224053..806aca017013 100644
--- a/www/bunkerweb/pkg-plist
+++ b/www/bunkerweb/pkg-plist
@@ -8540,11 +8540,12 @@
%%DATADIR%%/ui/utils/gunicorn.conf.py
%%DATADIR%%/ui/utils/logger.py
%%DATADIR%%/ui/utils/tmp-gunicorn.conf.py
+@(root,wheel,0440) etc/sudoers.d/bunkerweb
@sample %%ETCDIR%%/api.yml.sample
@dir %%ETCDIR%%/plugins
@dir %%ETCDIR%%/pro/plugins
-@dir(bunkerweb,bunkerweb,) /var/cache/bunkerweb/bunkernet
-@dir(bunkerweb,bunkerweb,) /var/cache/bunkerweb
-@dir(bunkerweb,bunkerweb,) /var/lib/bunkerweb
+@dir(www,www,) /var/cache/bunkerweb/bunkernet
+@dir(www,www,) /var/cache/bunkerweb
+@dir(www,www,) /var/lib/bunkerweb
@dir /var/lib
-@dir(bunkerweb,bunkerweb,) /var/log/bunkerweb
+@dir(www,www,) /var/log/bunkerweb