From eaef592014647e4bd9c555cd11463fee17baec09 Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Sat, 26 Sep 2026 10:24:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(bootstrap):=20offsite=5Fenabled=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=85=88=E5=8F=96=E5=8F=98=E9=87=8F=E5=86=8D=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=EF=BC=8C=E9=87=8D=E8=B7=91=E4=B8=8D=E5=86=8D=E5=9B=A0?= =?UTF-8?q?=20SIGPIPE=20=E7=AB=9E=E6=80=81=E8=AF=AF=E5=88=A0=20off-site=20?= =?UTF-8?q?timer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/bootstrap.sh | 15 ++++++++++----- deploy/bootstrap_test.sh | 13 +++++++++++-- docs/troubleshooting.md | 6 +++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index 7162417..1adbadd 100644 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -3227,9 +3227,8 @@ auth_hostname() { # auth_lines is the body of the [auth] section this run writes: the carried keys, after # the two hostnames derived from the root domain when the carry lacks them. A first # install gets exactly the two derived lines; a re-run reproduces the carried section. -# The keys are matched in a here-string, never `printf | grep -q`: grep exits at the -# match, printf dies of SIGPIPE on the lines after it, pipefail fails the test, and a -# second admin_hostname then breaks the file (see postgres_installed). +# The keys are matched in a here-string, never `printf | grep -q` (see offsite_enabled): +# a lost race there wrote a second admin_hostname, and the duplicate key broke the file. auth_lines() { local carried carried="$(persisted_auth_lines)" @@ -3352,9 +3351,15 @@ offsite_block() { if [ -n "$FELIS_OFFSITE_DB_KEEP" ]; then printf 'db_keep = %s\n' "$FELIS_OFFSITE_DB_KEEP"; fi } -# offsite_enabled: the [offsite] section this run writes names a bucket. +# offsite_enabled: the [offsite] section this run writes names a bucket. The section is +# read into a variable before matching (as in postgres_installed): in `offsite_block | +# grep -q` grep exits at the bucket line, the lines after it then kill offsite_block with +# SIGPIPE, and pipefail made that "no bucket". A re-run that lost the race removed the +# off-site timer and ended with NO OFF-SITE COPY on a host that has one. offsite_enabled() { - offsite_block | grep -Eq '^[[:space:]]*bucket[[:space:]]*=[[:space:]]*"[^"]+"' + local block + block="$(offsite_block)" + grep -Eq '^[[:space:]]*bucket[[:space:]]*=[[:space:]]*"[^"]+"' <<<"$block" } write_felis_toml() { diff --git a/deploy/bootstrap_test.sh b/deploy/bootstrap_test.sh index e9ae696..bf83b8d 100644 --- a/deploy/bootstrap_test.sh +++ b/deploy/bootstrap_test.sh @@ -1577,8 +1577,8 @@ DNS.2 = play.example.org' "$out" # The hostnames on the section's first lines and the section far past one pipe buffer: # a `printf | grep -q` or `| awk exit` over it has the reader exit while printf is still -# writing, which is certain here and a scheduling race on a real host. The config must -# still carry each name once, and +# writing, which is certain here and a scheduling race on a real host (offsite_enabled +# lost it, see the off-site section). The config must still carry each name once, and # the helpers must fail nothing along the way (the installer logs every failed command). { printf '[auth]\nadmin_hostname = "ops.example.org"\npanel_hostname = "play.example.org"\n' seq 1 200000 | sed 's/.*/k& = "v"/'; } > "$adir/felis.host.toml" @@ -1879,6 +1879,15 @@ case "$out" in *) echo "PASS the carried [offsite] stops at the next section" ;; esac +# A re-run found a configured bucket "missing" and removed the off-site timer: grep -q +# stopped at the bucket line while offsite_block was still writing, and pipefail turned +# the SIGPIPE into "no bucket". The section far past one pipe buffer with the bucket near +# its top makes the lost race certain. +{ printf '[offsite]\nbucket = "kept"\nendpoint = "https://s3.example"\n' + seq 1 200000 | sed 's/.*/prefix = "p&"/'; } > "$odir/felis.host.toml" +out="$(run_offsite 'if offsite_enabled; then echo ENABLED; else echo "OFF (exit $?)"; fi')" +expect "a configured bucket is found in a long [offsite] section" "ENABLED" "$out" + # First configured install: the key is generated, the file is private, the key is shown once. rm -f "$odir/felis.host.toml" out="$(FELIS_OFFSITE_ENDPOINT=https://s3.example FELIS_OFFSITE_BUCKET=felis \ diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e63b236..4caea97 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2054,7 +2054,11 @@ a generated encryption key in `/etc/felis/offsite.env` (mode 0600), and sealed objects, and without the key they cannot be read. A later re-run keeps the key; it refuses a `FELIS_OFFSITE_KEY` that differs from the one in `offsite.env`, since every object already in the bucket is sealed with it. -Without a bucket the installer ends with `NO OFF-SITE COPY`. +Without a bucket the installer ends with `NO OFF-SITE COPY`. Installers before +the fix for a pipe race in reading `[offsite]` could also end that way on a +host that has a bucket, and removed `felis-offsite.timer` as they did; a re-run +of the current installer puts it back. `systemctl list-timers felis-offsite.timer` +shows whether the timer is there. [SH-TESTED] [VM-TESTED: a re-run that lost the race] What runs: