diff --git a/README.md b/README.md
index 6322c89..626cef7 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ The Linux-side package builder validates the FreeBSD package payload before publ
## OPNsense UI
-The OPNsense page **Services -> RustProxy** exposes enable/disable plus HTTP, HTTPS/TLS and administration listener settings. RustProxy runs as the unprivileged `rustproxy` user.
+The OPNsense page **Services -> RustProxy** exposes enable/disable plus separate bind-address and port settings for HTTP, HTTPS/TLS and administration. On OPNsense/FreeBSD RustProxy runs as `root`.
## Validation
@@ -87,12 +87,12 @@ See `docs/FIRST_RUN.md` for Debian setup.
The OPNsense page **Services -> RustProxy** exposes the runtime listeners used by the rc.d service:
-- HTTP listen: `0.0.0.0:8080`
-- HTTPS/TLS listen: `0.0.0.0:8181` (toggleable)
-- Admin listen: `0.0.0.0:9090`
+- HTTP bind address + port: `0.0.0.0` / `8080`
+- HTTPS/TLS bind address + port: `0.0.0.0` / `8181` (toggleable)
+- Admin bind address + port: `0.0.0.0` / `9090`
- Allow remote admin: on by default so the RustProxy panel can be reached from other hosts; OPNsense firewall rules still decide which networks can connect
-The service runs as the unprivileged `rustproxy` user and keeps `/var/db/rustproxy` mode `0700`, as required by RustProxy. Package installation restarts `configd` so `configctl rustproxy ...` actions are immediately available even after direct `pkg add`.
+On OPNsense/FreeBSD the service runs as `root` and keeps `/var/db/rustproxy` mode `0700`. This avoids startup/access failures seen after privilege dropping with `daemon -u rustproxy`. Package installation restarts `configd` so `configctl rustproxy ...` actions are immediately available even after direct `pkg add`.
## FreeBSD/OPNsense-only RustProxy patches
@@ -108,4 +108,8 @@ Set `APPLY_FREEBSD_PATCHES=0` in `/etc/rustproxy-opnsense-builder.env` only when
### Runtime PID directory
-The OPNsense rc.d service creates `/var/run/rustproxy` as `rustproxy:rustproxy` (0750) on every start. Both supervisor and child PID files live there so `daemon -u rustproxy` can run without root-owned PID-file errors.
+The OPNsense rc.d service creates `/var/run/rustproxy` as `root:wheel` (0750) on every start. Both supervisor and child PID files live there. The runtime is kept as root on OPNsense/FreeBSD because privilege dropping caused real-world startup and data/certificate access failures.
+
+### Listener compatibility note
+
+Plugin model 1.3 stores bind addresses and ports separately. This prevents malformed rendered arguments such as `--tls-listen 0.0.0.0:`. Existing 1.2.x combined `proxyListen`, `tlsListen` and `adminListen` values remain accepted by rc.d during upgrades. If a legacy value is malformed, rc.d falls back to the documented default instead of passing an invalid listener to RustProxy.
diff --git a/docs/FIRST_RUN.md b/docs/FIRST_RUN.md
index cb7ddec..a3debbf 100644
--- a/docs/FIRST_RUN.md
+++ b/docs/FIRST_RUN.md
@@ -143,17 +143,17 @@ Leave both empty for manual deployment.
After installation open **Services -> RustProxy**. Defaults are:
-- HTTP listen: `0.0.0.0:8080`
+- HTTP bind address / port: `0.0.0.0` / `8080`
- HTTPS/TLS enabled: yes
-- HTTPS/TLS listen: `0.0.0.0:8181`
-- Admin listen: `0.0.0.0:9090`
+- HTTPS/TLS bind address / port: `0.0.0.0` / `8181`
+- Admin bind address / port: `0.0.0.0` / `9090`
- Allow remote admin: yes
-The RustProxy administration UI is reachable from other hosts by default on port `9090`, subject to OPNsense firewall rules. Use **Open RustProxy Admin** after Apply to open the panel in a new browser tab. If `Admin listen` is `0.0.0.0:9090`, the button uses the hostname or IP address through which you opened OPNsense.
+The RustProxy administration UI is reachable from other hosts by default on port `9090`, subject to OPNsense firewall rules. Use **Open RustProxy Admin** after Apply to open the panel in a new browser tab. If the admin bind address is `0.0.0.0`, the button uses the hostname or IP address through which you opened OPNsense.
Direct `pkg add` is supported: the package post-install script restarts OPNsense `configd`, flushes the plugin cache and renders `/usr/local/etc/rustproxy/opnsense.conf` so `configctl rustproxy status/start/stop` is available immediately.
-RustProxy requires its data directory to be private. The plugin enforces owner `rustproxy:rustproxy` and mode `0700` on `/var/db/rustproxy` before every start.
+RustProxy requires its data directory to be private. On OPNsense/FreeBSD the plugin runs the service as `root` and enforces owner `root:wheel` with mode `0700` on `/var/db/rustproxy` before every start.
## FreeBSD patch set
@@ -185,4 +185,4 @@ Applying FreeBSD/OPNsense patch set for RustProxy 0.20.0 to isolated build sourc
## Runtime directory
-`/var/run/rustproxy` is created automatically as `rustproxy:rustproxy` with mode 0750 during package installation and before each service start. PID files are `daemon.pid` and `rustproxy.pid` inside that directory.
+`/var/run/rustproxy` is created automatically as `root:wheel` with mode 0750 during package installation and before each service start. PID files are `daemon.pid` and `rustproxy.pid` inside that directory.
diff --git a/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre b/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre
index 1b6509d..a19b863 100755
--- a/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre
+++ b/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre
@@ -1,5 +1,7 @@
#!/bin/sh
+# Keep the historical service account so package downgrades remain possible,
+# but current OPNsense runtime executes RustProxy as root (see rc.d script).
if ! /usr/sbin/pw groupshow rustproxy >/dev/null 2>&1; then
/usr/sbin/pw groupadd rustproxy
fi
@@ -8,11 +10,11 @@ if ! /usr/sbin/pw usershow rustproxy >/dev/null 2>&1; then
/usr/sbin/pw useradd rustproxy -g rustproxy -c "RustProxy daemon" -d /var/db/rustproxy -s /usr/sbin/nologin
fi
-/usr/bin/install -d -o rustproxy -g rustproxy -m 0700 /var/db/rustproxy
-/usr/sbin/chown rustproxy:rustproxy /var/db/rustproxy
+/usr/bin/install -d -o root -g wheel -m 0700 /var/db/rustproxy
+/usr/sbin/chown -R root:wheel /var/db/rustproxy
/bin/chmod 0700 /var/db/rustproxy
-/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/log/rustproxy
-/usr/bin/install -d -o rustproxy -g rustproxy -m 0750 /var/run/rustproxy
+/usr/bin/install -d -o root -g wheel -m 0750 /var/log/rustproxy
+/usr/bin/install -d -o root -g wheel -m 0750 /var/run/rustproxy
/usr/bin/install -d -o root -g wheel -m 0755 /usr/local/etc/rustproxy
# configd discovers actions only on startup/reload. A direct `pkg add` must
diff --git a/opnsense-plugin/www/rustproxy/Makefile b/opnsense-plugin/www/rustproxy/Makefile
index d45cdfa..546dce8 100644
--- a/opnsense-plugin/www/rustproxy/Makefile
+++ b/opnsense-plugin/www/rustproxy/Makefile
@@ -1,5 +1,5 @@
PLUGIN_NAME= rustproxy
-PLUGIN_VERSION?= 0.20.0
+PLUGIN_VERSION?= 0.23.3
PLUGIN_REVISION?= 0
PLUGIN_COMMENT= RustProxy reverse proxy service
PLUGIN_MAINTAINER?= local@localhost
diff --git a/opnsense-plugin/www/rustproxy/pkg-descr b/opnsense-plugin/www/rustproxy/pkg-descr
index 5b5fc36..f41d8c3 100644
--- a/opnsense-plugin/www/rustproxy/pkg-descr
+++ b/opnsense-plugin/www/rustproxy/pkg-descr
@@ -1,4 +1,4 @@
RustProxy packaged as an OPNsense service plugin.
-The OPNsense UI exposes service enable/disable, HTTP and HTTPS/TLS listener
-addresses, the administration listener and the remote-admin opt-in.
+The OPNsense UI exposes service enable/disable, separate bind-address and
+port fields for HTTP, HTTPS/TLS and administration, plus remote-admin opt-in.
diff --git a/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy b/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy
index 8f70e06..6907b0c 100755
--- a/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy
+++ b/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy
@@ -12,20 +12,38 @@ rcvar="${name}_enable"
load_rc_config "$name"
: ${rustproxy_enable:="NO"}
-: ${rustproxy_user:="rustproxy"}
-: ${rustproxy_group:="rustproxy"}
+# RustProxy on OPNsense/FreeBSD runs as root. In practice the application
+# needs access to its complete data/certificate state and service startup was
+# unreliable after daemon(8) dropped privileges to the dedicated account.
+: ${rustproxy_user:="root"}
+: ${rustproxy_group:="wheel"}
: ${rustproxy_data_dir:="/var/db/rustproxy"}
: ${rustproxy_log_dir:="/var/log/rustproxy"}
: ${rustproxy_log_file:="${rustproxy_log_dir}/rustproxy.log"}
: ${rustproxy_run_dir:="/var/run/rustproxy"}
: ${rustproxy_command:="/usr/local/sbin/rust-proxy"}
: ${rustproxy_restart_delay:="5"}
-: ${rustproxy_proxy_listen:="0.0.0.0:8080"}
-: ${rustproxy_tls_enabled:="1"}
-: ${rustproxy_tls_listen:="0.0.0.0:8181"}
-: ${rustproxy_admin_listen:="0.0.0.0:9090"}
: ${rustproxy_allow_remote_admin:="1"}
+# Split listener settings written by the current OPNsense model.
+: ${rustproxy_proxy_address:=""}
+: ${rustproxy_proxy_port:=""}
+: ${rustproxy_tls_enabled:="1"}
+: ${rustproxy_tls_address:=""}
+: ${rustproxy_tls_port:=""}
+: ${rustproxy_admin_address:=""}
+: ${rustproxy_admin_port:=""}
+
+# Combined listener settings used by plugin model 1.2.x and earlier.
+: ${rustproxy_proxy_listen_legacy:=""}
+: ${rustproxy_tls_listen_legacy:=""}
+: ${rustproxy_admin_listen_legacy:=""}
+
+# Old rc.conf-style variable names are also accepted for hand-written configs.
+: ${rustproxy_proxy_listen:=""}
+: ${rustproxy_tls_listen:=""}
+: ${rustproxy_admin_listen:=""}
+
enabled="0"
config_file="/usr/local/etc/rustproxy/opnsense.conf"
if [ -r "$config_file" ]; then
@@ -38,9 +56,94 @@ else
rustproxy_enable="NO"
fi
-rustproxy_runtime_args="--data-dir ${rustproxy_data_dir} --admin-listen ${rustproxy_admin_listen} --update-check-interval-secs 0 serve --proxy-listen ${rustproxy_proxy_listen}"
+rustproxy_valid_port()
+{
+ case "$1" in
+ ''|*[!0-9]*) return 1 ;;
+ esac
+ [ "$1" -ge 1 ] 2>/dev/null && [ "$1" -le 65535 ] 2>/dev/null
+}
+
+rustproxy_valid_listener()
+{
+ _rp_listener="$1"
+ case "$_rp_listener" in
+ \[*\]:*) _rp_port="${_rp_listener##*:}" ;;
+ *:*) _rp_port="${_rp_listener##*:}" ;;
+ *) return 1 ;;
+ esac
+ rustproxy_valid_port "$_rp_port"
+}
+
+rustproxy_format_listener()
+{
+ _rp_address="$1"
+ _rp_port="$2"
+ case "$_rp_address" in
+ \[*\]) printf '%s:%s\n' "$_rp_address" "$_rp_port" ;;
+ *:*) printf '[%s]:%s\n' "$_rp_address" "$_rp_port" ;;
+ *) printf '%s:%s\n' "$_rp_address" "$_rp_port" ;;
+ esac
+}
+
+rustproxy_resolve_listener()
+{
+ _rp_address="$1"
+ _rp_port="$2"
+ _rp_legacy="$3"
+ _rp_old_rc="$4"
+ _rp_default_address="$5"
+ _rp_default_port="$6"
+ _rp_name="$7"
+
+ # New split settings are authoritative as soon as either half is set.
+ if [ -n "$_rp_address" ] || [ -n "$_rp_port" ]; then
+ [ -n "$_rp_address" ] || _rp_address="$_rp_default_address"
+ [ -n "$_rp_port" ] || _rp_port="$_rp_default_port"
+ if ! rustproxy_valid_port "$_rp_port"; then
+ echo "Invalid RustProxy ${_rp_name} port: ${_rp_port}" >&2
+ return 1
+ fi
+ rustproxy_format_listener "$_rp_address" "$_rp_port"
+ return 0
+ fi
+
+ # Preserve a valid listener from plugin 1.2.x during upgrade.
+ if [ -n "$_rp_legacy" ] && rustproxy_valid_listener "$_rp_legacy"; then
+ printf '%s\n' "$_rp_legacy"
+ return 0
+ fi
+
+ # Preserve hand-written rc.conf settings as another compatibility path.
+ if [ -n "$_rp_old_rc" ] && rustproxy_valid_listener "$_rp_old_rc"; then
+ printf '%s\n' "$_rp_old_rc"
+ return 0
+ fi
+
+ # Malformed legacy values such as "0.0.0.0:" must never reach RustProxy.
+ # Fall back to a known-good listener and let the user set the separate port
+ # field in Services -> RustProxy.
+ rustproxy_format_listener "$_rp_default_address" "$_rp_default_port"
+}
+
+rustproxy_proxy_listen_resolved="$(rustproxy_resolve_listener \
+ "$rustproxy_proxy_address" "$rustproxy_proxy_port" \
+ "$rustproxy_proxy_listen_legacy" "$rustproxy_proxy_listen" \
+ "0.0.0.0" "8080" "HTTP")" || exit 1
+
+rustproxy_tls_listen_resolved="$(rustproxy_resolve_listener \
+ "$rustproxy_tls_address" "$rustproxy_tls_port" \
+ "$rustproxy_tls_listen_legacy" "$rustproxy_tls_listen" \
+ "0.0.0.0" "8181" "HTTPS/TLS")" || exit 1
+
+rustproxy_admin_listen_resolved="$(rustproxy_resolve_listener \
+ "$rustproxy_admin_address" "$rustproxy_admin_port" \
+ "$rustproxy_admin_listen_legacy" "$rustproxy_admin_listen" \
+ "0.0.0.0" "9090" "admin")" || exit 1
+
+rustproxy_runtime_args="--data-dir ${rustproxy_data_dir} --admin-listen ${rustproxy_admin_listen_resolved} --update-check-interval-secs 0 serve --proxy-listen ${rustproxy_proxy_listen_resolved}"
if [ "$rustproxy_tls_enabled" = "1" ]; then
- rustproxy_runtime_args="${rustproxy_runtime_args} --tls-listen ${rustproxy_tls_listen}"
+ rustproxy_runtime_args="${rustproxy_runtime_args} --tls-listen ${rustproxy_tls_listen_resolved}"
fi
if [ "$rustproxy_allow_remote_admin" = "1" ]; then
rustproxy_runtime_args="${rustproxy_runtime_args} --allow-remote-admin"
@@ -49,16 +152,19 @@ fi
pidfile="${rustproxy_run_dir}/daemon.pid"
child_pidfile="${rustproxy_run_dir}/rustproxy.pid"
command="/usr/sbin/daemon"
-command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} -u ${rustproxy_user} -o ${rustproxy_log_file} ${rustproxy_command} ${rustproxy_runtime_args}"
+
+rustproxy_daemon_user_arg=""
+if [ "$rustproxy_user" != "root" ]; then
+ rustproxy_daemon_user_arg="-u ${rustproxy_user}"
+fi
+command_args="-r -R ${rustproxy_restart_delay} -P ${pidfile} -p ${child_pidfile} ${rustproxy_daemon_user_arg} -o ${rustproxy_log_file} ${rustproxy_command} ${rustproxy_runtime_args}"
required_files="${rustproxy_command}"
start_precmd="rustproxy_prestart"
stop_postcmd="rustproxy_poststop"
rustproxy_prestart()
{
- # /var/run is ephemeral and recreated on boot. daemon(8) writes the
- # child pidfile after dropping privileges to rustproxy, so the runtime
- # directory must be writable by the service account.
+ # /var/run is ephemeral and recreated on boot.
/usr/bin/install -d -o "${rustproxy_user}" -g "${rustproxy_group}" -m 0750 "${rustproxy_run_dir}"
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_run_dir}"
/bin/chmod 0750 "${rustproxy_run_dir}"
@@ -71,6 +177,8 @@ rustproxy_prestart()
/usr/bin/touch "${rustproxy_log_file}"
/usr/sbin/chown "${rustproxy_user}:${rustproxy_group}" "${rustproxy_log_file}"
/bin/chmod 0640 "${rustproxy_log_file}"
+
+ echo "RustProxy listeners: HTTP=${rustproxy_proxy_listen_resolved} TLS=${rustproxy_tls_listen_resolved} ADMIN=${rustproxy_admin_listen_resolved}"
}
rustproxy_poststop()
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml
index 291db73..7d599f6 100644
--- a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml
+++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/controllers/OPNsense/RustProxy/forms/general.xml
@@ -10,16 +10,29 @@
checkboxStart RustProxy and keep it enabled after OPNsense restarts.
+
-
+
headertrue
- rustproxy.general.proxyListen
-
+ rustproxy.general.proxyAddress
+
text
- HTTP listener passed as --proxy-listen. Default: 0.0.0.0:8080.
+ HTTP bind address. Leave empty for 0.0.0.0 or to keep the legacy combined listener after an upgrade.
+
+
+ rustproxy.general.proxyPort
+
+ text
+ HTTP port passed as --proxy-listen. Leave empty for 8080 or to keep the legacy combined listener after an upgrade.
+
+
+
+
+ header
+ truerustproxy.general.tlsEnabled
@@ -28,21 +41,34 @@
When enabled, pass --tls-listen to RustProxy.
- rustproxy.general.tlsListen
-
+ rustproxy.general.tlsAddress
+
text
- HTTPS/TLS listener passed as --tls-listen. Default: 0.0.0.0:8181.
+ HTTPS/TLS bind address. Leave empty for 0.0.0.0 or to keep the legacy combined listener after an upgrade.
+
+ rustproxy.general.tlsPort
+
+ text
+ HTTPS/TLS port passed as --tls-listen. Leave empty for 8181 or to keep the legacy combined listener after an upgrade.
+
+
headertrue
- rustproxy.general.adminListen
-
+ rustproxy.general.adminAddress
+
text
- RustProxy administration listener passed as --admin-listen. Default: 0.0.0.0:9090 so the panel can be reached from other hosts. OPNsense firewall rules still control who can connect.
+ Administration bind address. Leave empty for 0.0.0.0 or to keep the legacy combined listener after an upgrade.
+
+
+ rustproxy.general.adminPort
+
+ text
+ Administration port passed as --admin-listen. Leave empty for 9090 or to keep the legacy combined listener after an upgrade.rustproxy.general.allowRemoteAdmin
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml
index bf3053d..1e56de7 100644
--- a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml
+++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml
@@ -1,6 +1,6 @@
//OPNsense/rustproxy
- 1.2.0
+ 1.3.0RustProxy service settings
@@ -8,29 +8,48 @@
0Y
-
- 0.0.0.0:8080
- Y
- Enter a valid HTTP listen address and port, for example 0.0.0.0:8080.
-
+
+
+
+ N
+ Enter a valid HTTP bind address, for example 0.0.0.0.
+
+
+ Enter a valid HTTP port between 1 and 65535.
+ 1Y
-
- 0.0.0.0:8181
- Y
- Enter a valid HTTPS/TLS listen address and port, for example 0.0.0.0:8181.
-
-
- 0.0.0.0:9090
- Y
- Enter a valid administration listen address and port, for example 0.0.0.0:9090.
-
+
+ N
+ Enter a valid HTTPS/TLS bind address, for example 0.0.0.0.
+
+
+ Enter a valid HTTPS/TLS port between 1 and 65535.
+
+
+ N
+ Enter a valid administration bind address, for example 0.0.0.0.
+
+
+ Enter a valid administration port between 1 and 65535.
+ 1Y
+
+
+
+
+
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt
index 2f4b957..bc7ce11 100644
--- a/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt
+++ b/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/views/OPNsense/RustProxy/index.volt
@@ -38,7 +38,18 @@ $(document).ready(function() {
const general = data && data.rustproxy && data.rustproxy.general
? data.rustproxy.general
: {};
- const listen = parseAdminListen(general.adminListen || '0.0.0.0:9090');
+ let listen = null;
+ const adminAddress = String(general.adminAddress || '').trim();
+ const adminPort = String(general.adminPort || '').trim();
+ if (adminAddress || adminPort) {
+ listen = {
+ host: adminAddress || '0.0.0.0',
+ port: adminPort || '9090'
+ };
+ } else {
+ // Upgrade compatibility with plugin model 1.2.x.
+ listen = parseAdminListen(general.adminListen || '0.0.0.0:9090');
+ }
const remoteValue = general.allowRemoteAdmin;
const allowRemote = remoteValue === true || remoteValue === 1 ||
['1', 'true', 'yes', 'on'].indexOf(String(remoteValue || '').toLowerCase()) !== -1;
diff --git a/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf
index 893a477..dc4cffb 100644
--- a/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf
+++ b/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf
@@ -1,6 +1,18 @@
enabled="{% if not helpers.empty('OPNsense.rustproxy.general.enabled') %}1{% else %}0{% endif %}"
-rustproxy_proxy_listen="{% if not helpers.empty('OPNsense.rustproxy.general.proxyListen') %}{{ OPNsense.rustproxy.general.proxyListen }}{% else %}0.0.0.0:8080{% endif %}"
+
+# New split listener fields. Empty values are resolved by rc.d using the
+# legacy combined field (upgrade path) and finally the built-in defaults.
+rustproxy_proxy_address="{% if not helpers.empty('OPNsense.rustproxy.general.proxyAddress') %}{{ OPNsense.rustproxy.general.proxyAddress }}{% endif %}"
+rustproxy_proxy_port="{% if not helpers.empty('OPNsense.rustproxy.general.proxyPort') %}{{ OPNsense.rustproxy.general.proxyPort }}{% endif %}"
rustproxy_tls_enabled="{% if not helpers.empty('OPNsense.rustproxy.general.tlsEnabled') %}1{% else %}0{% endif %}"
-rustproxy_tls_listen="{% if not helpers.empty('OPNsense.rustproxy.general.tlsListen') %}{{ OPNsense.rustproxy.general.tlsListen }}{% else %}0.0.0.0:8181{% endif %}"
-rustproxy_admin_listen="{% if not helpers.empty('OPNsense.rustproxy.general.adminListen') %}{{ OPNsense.rustproxy.general.adminListen }}{% else %}0.0.0.0:9090{% endif %}"
+rustproxy_tls_address="{% if not helpers.empty('OPNsense.rustproxy.general.tlsAddress') %}{{ OPNsense.rustproxy.general.tlsAddress }}{% endif %}"
+rustproxy_tls_port="{% if not helpers.empty('OPNsense.rustproxy.general.tlsPort') %}{{ OPNsense.rustproxy.general.tlsPort }}{% endif %}"
+rustproxy_admin_address="{% if not helpers.empty('OPNsense.rustproxy.general.adminAddress') %}{{ OPNsense.rustproxy.general.adminAddress }}{% endif %}"
+rustproxy_admin_port="{% if not helpers.empty('OPNsense.rustproxy.general.adminPort') %}{{ OPNsense.rustproxy.general.adminPort }}{% endif %}"
rustproxy_allow_remote_admin="{% if not helpers.empty('OPNsense.rustproxy.general.allowRemoteAdmin') %}1{% else %}0{% endif %}"
+
+# Legacy 1.2.x combined settings. They are only used when the corresponding
+# new split address/port fields are both empty.
+rustproxy_proxy_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.proxyListen') %}{{ OPNsense.rustproxy.general.proxyListen }}{% endif %}"
+rustproxy_tls_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.tlsListen') %}{{ OPNsense.rustproxy.general.tlsListen }}{% endif %}"
+rustproxy_admin_listen_legacy="{% if not helpers.empty('OPNsense.rustproxy.general.adminListen') %}{{ OPNsense.rustproxy.general.adminListen }}{% endif %}"
diff --git a/scripts/check.sh b/scripts/check.sh
index e62f499..2eb4727 100755
--- a/scripts/check.sh
+++ b/scripts/check.sh
@@ -51,9 +51,33 @@ grep -q 'PACKAGING_WORKDIR=/opt/opn-rustproxy/packaging' "$ROOT/builder.env.exam
[[ -f "$ROOT/builder/freebsd/patches/0.20.0/0001-opnsense-build.patch" ]]
grep -q 'APPLY_FREEBSD_PATCHES=1' "$ROOT/builder.env.example"
grep -q 'builder/freebsd/apply-patches.sh' "$ROOT/builder/debian/build-docker-release.sh"
-grep -q 'rustproxy_run_dir:="/var/run/rustproxy"' "$ROOT/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy"
-grep -q 'pidfile="${rustproxy_run_dir}/daemon.pid"' "$ROOT/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy"
-grep -q 'child_pidfile="${rustproxy_run_dir}/rustproxy.pid"' "$ROOT/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy"
-grep -q '/var/run/rustproxy' "$ROOT/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre"
+
+rc_script="$ROOT/opnsense-plugin/www/rustproxy/src/etc/rc.d/rustproxy"
+model="$ROOT/opnsense-plugin/www/rustproxy/src/opnsense/mvc/app/models/OPNsense/RustProxy/RustProxy.xml"
+template="$ROOT/opnsense-plugin/www/rustproxy/src/opnsense/service/templates/OPNsense/RustProxy/rustproxy.conf"
+post_install="$ROOT/opnsense-plugin/www/rustproxy/+POST_INSTALL.pre"
+
+grep -q 'rustproxy_run_dir:="/var/run/rustproxy"' "$rc_script"
+grep -q 'rustproxy_user:="root"' "$rc_script"
+grep -q 'rustproxy_group:="wheel"' "$rc_script"
+grep -q 'rustproxy_resolve_listener' "$rc_script"
+grep -q 'rustproxy_valid_port' "$rc_script"
+grep -q 'pidfile="${rustproxy_run_dir}/daemon.pid"' "$rc_script"
+grep -q 'child_pidfile="${rustproxy_run_dir}/rustproxy.pid"' "$rc_script"
+grep -q '' "$model"
+grep -q '' "$model"
+grep -q '' "$model"
+grep -q 'rustproxy_tls_port=' "$template"
+grep -q 'rustproxy_tls_listen_legacy=' "$template"
+grep -q 'root -g wheel' "$post_install"
+grep -q '/var/run/rustproxy' "$post_install"
+
+# The generated command must never contain a literal listener ending in a colon
+# from model defaults/templates. rc.d rejects/falls back from malformed legacy
+# values before assembling runtime_args.
+if grep -R --exclude='*.md' --exclude='check.sh' -nE 'tls_listen.*0\.0\.0\.0:"|--tls-listen 0\.0\.0\.0:' "$ROOT/opnsense-plugin"; then
+ echo "Malformed empty-port TLS listener found in plugin source" >&2
+ exit 1
+fi
echo "Repository checks passed."