From 267cb3314d873a46946a9e3f2dcb4ac8d942509b Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Fri, 7 Aug 2026 16:32:31 +0300 Subject: [PATCH] fix(deploy): quote --rsync-path, and let systemd own the state directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run 4 failed at `ship artifacts` with `sudo: unrecognized option '--server'`. `R="--rsync-path=sudo rsync --mkpath"` expanded unquoted as `rsync $R` splits into three arguments — `--rsync-path=sudo`, plus a stray `rsync` that rsync reads as a source path — so the remote end ran `sudo --server`. Use an array. The dashboard step quoted it inline and was unaffected, which is why only half the deploy was broken. Every rsync destination and every sudo command in `apply system state` has now been exercised directly against bob as gitea_ci, rather than by another six minute round trip: seven rsync targets, sysusers, restorecon, firewalld and daemon-reload all pass. That surfaced the second fault. restorecon was given /var/lib/tireless, which does not exist on a fresh host: infra-setup.sh tried to create it before systemd-sysusers had created the account to own it, so the attempt always raced and always lost. Declare StateDirectory=tireless on all three units instead — systemd creates the directory, owns it as the service user and labels it — and drop the path from restorecon, the grant, and infra-setup. Refs #9 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6 --- .gitea/workflows/deploy.yaml | 30 +++++++++++++++++++-------- asset/systemd/tireless-api.service | 7 ++++++- asset/systemd/tireless-poller.service | 7 ++++++- asset/systemd/tireless-runner.service | 7 ++++++- script/infra-setup.sh | 11 ++++++---- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 9f12b60..fe71674 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -96,23 +96,32 @@ jobs: - name: ship artifacts run: | - R="--rsync-path=sudo rsync --mkpath" + # An array, not a string. `R="--rsync-path=sudo rsync --mkpath"` used + # as `rsync $R` word-splits into three arguments — `--rsync-path=sudo` + # plus a stray `rsync` that rsync reads as a source path — and the + # remote end runs `sudo --server`, which sudo rejects. + # + # --mkpath because rsync will not create a missing destination + # directory for a single-file copy, and Fedora ships neither + # /etc/sysusers.d nor /etc/firewalld/services + # (architecture/deployment-gitea-actions.md §6). + R=(--rsync-path="sudo rsync --mkpath") B=target/x86_64-unknown-linux-musl/release - rsync $R --chmod 0755 "$B/tireless-api" \ + rsync "${R[@]}" --chmod 0755 "$B/tireless-api" \ gitea_ci@"$API_HOST":/usr/local/bin/tireless-api - rsync $R --chmod 0755 "$B/tireless-worker" \ + rsync "${R[@]}" --chmod 0755 "$B/tireless-worker" \ gitea_ci@"$API_HOST":/usr/local/bin/tireless-worker - rsync $R --chmod 0755 "$B/tireless" \ + rsync "${R[@]}" --chmod 0755 "$B/tireless" \ gitea_ci@"$API_HOST":/usr/local/bin/tireless - rsync $R --chmod 0640 config.toml \ + rsync "${R[@]}" --chmod 0640 config.toml \ gitea_ci@"$API_HOST":/etc/tireless/config.toml - rsync $R asset/systemd/tireless.sysusers.conf \ + rsync "${R[@]}" asset/systemd/tireless.sysusers.conf \ gitea_ci@"$API_HOST":/etc/sysusers.d/tireless.conf for unit in tireless-api tireless-poller tireless-runner; do - rsync $R "asset/systemd/$unit.service" \ + rsync "${R[@]}" "asset/systemd/$unit.service" \ gitea_ci@"$API_HOST":"/etc/systemd/system/$unit.service" done - rsync $R asset/firewalld/tireless-api.xml \ + rsync "${R[@]}" asset/firewalld/tireless-api.xml \ gitea_ci@"$API_HOST":/etc/firewalld/services/tireless-api.xml - name: ship dashboard @@ -129,8 +138,11 @@ jobs: run: | ssh gitea_ci@"$API_HOST" bash -euo pipefail <