From 5b6fb431ff6fa58456878b01e6df39a76f2e2eeb Mon Sep 17 00:00:00 2001 From: Rob Thijssen Date: Mon, 31 Aug 2026 18:14:07 +0300 Subject: [PATCH] fix: quote firewalld rich rules for the remote shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() is `ssh ... "$@"`, and ssh concatenates its argument vector with spaces for the remote shell to re-split, so local quoting is lost. The rich rule arrived as a dozen bare words: firewall-cmd: error: unrecognized arguments: family=ipv4 source address=10.6.0.146/32 service name=quantus-node-miner accept Pass the three rich-rule invocations as one pre-quoted string each. Verified against the real host as gitea_ci: the unquoted form reproduces the CI error, the quoted form returns 'no' — so it parses and the scoped sudoers rule matches it. Every other command in the deploy survived only because no other argument contains a space; CLAUDE.md now records the trap. --- .gitea/workflows/deploy.yaml | 13 ++++++++++--- CLAUDE.md | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 3c5ce36..6588a92 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -247,11 +247,18 @@ jobs: *) echo "refusing to open ${{ env.MINER_LINK_PORT }}/udp to non-mesh address '${miner_ip}' for ${m}" >&2; exit 1 ;; esac rich="rule family=ipv4 source address=${miner_ip}/32 service name=quantus-node-miner accept" - if run sudo firewall-cmd --zone="$zone" --query-rich-rule="$rich"; then + # Pass these as ONE pre-quoted string, not as separate run() args. + # `run()` is `ssh ... "$@"`, and ssh concatenates its argument vector + # with spaces for the REMOTE shell to re-split — so local quoting is + # lost and a rich rule arrives as a dozen bare words + # ("unrecognized arguments: family=ipv4 source address=..."). Every + # other command here survives only because no other argument + # contains a space. Keep the inner single quotes. + if run "sudo firewall-cmd --zone=$zone --query-rich-rule='$rich'"; then echo "firewalld: rich rule for ${m} already present in ${zone}" else - run sudo firewall-cmd --permanent --zone="$zone" --add-rich-rule="$rich" - run sudo firewall-cmd --zone="$zone" --add-rich-rule="$rich" + run "sudo firewall-cmd --permanent --zone=$zone --add-rich-rule='$rich'" + run "sudo firewall-cmd --zone=$zone --add-rich-rule='$rich'" fi done diff --git a/CLAUDE.md b/CLAUDE.md index 4e68f31..1af0a1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,21 @@ without re-measuring: - Node Prometheus is on **9615**, not 9616. Upstream's `MINING.md` parameter table is wrong about this; the running binary is the authority. +## ssh argument quoting + +`run()` in the deploy is `ssh ... "$@"`. ssh joins its argument vector with +spaces and the REMOTE shell re-splits it, so any argument containing a space +must be passed as one pre-quoted string: + +```sh +run "sudo firewall-cmd --zone=$zone --add-rich-rule='$rich'" # correct +run sudo firewall-cmd --zone="$zone" --add-rich-rule="$rich" # word-splits remotely +``` + +This bit the firewalld rich rules on run 1 and nothing else, because no other +argument in the deploy contains a space. Check this before adding any command +with a quoted multi-word argument. + ## Working on the deploy - The workflow is the source of infra truth (`deployment-gitea-actions.md`).