From 13348e6c274fd19da464acceb6304e02196e85aa Mon Sep 17 00:00:00 2001 From: Ant Somers Date: Mon, 5 Oct 2026 13:28:52 +0300 Subject: [PATCH 1/2] fix: copy the keystore credential to a 0600 runtime file in sponsord.service systemd 255 loads LoadCredential= files at 0440, and decdn's keystore check refuses any group or other bit, so the reference unit crash-looped on Ubuntu 24.04. Copy the keystore into a private RuntimeDirectory at 0600 in ExecStartPre and point SPONSORD_TREASURY_KEYSTORE there. The copy is still the encrypted keystore; the password stays in %d. Closes #36 Co-Authored-By: Claude Opus 5.5 --- crates/sponsord/CHANGELOG.md | 7 +++++++ deploy/systemd/sponsord.service | 8 +++++++- docs/operator.md | 4 +++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/sponsord/CHANGELOG.md b/crates/sponsord/CHANGELOG.md index ab4ffa7..92ab4d9 100644 --- a/crates/sponsord/CHANGELOG.md +++ b/crates/sponsord/CHANGELOG.md @@ -40,3 +40,10 @@ release. `github.com/decdn/decdn`, at the commit `Cargo.lock` pins (`7f938e6b8437eba614e54375dcbc504de108379d`); `decdn.ref` and the sibling checkout are gone. + +### Fixed + +- **systemd unit on systemd 255.** `deploy/systemd/sponsord.service` copies + the keystore credential to a `0600` file under `/run/sponsord` before start. + systemd 255 loads credentials at `0440`, which the daemon refused as an + insecure keystore ([#36](https://github.com/decdn/sponsord/issues/36)). diff --git a/deploy/systemd/sponsord.service b/deploy/systemd/sponsord.service index 4a9a17d..1a607f8 100644 --- a/deploy/systemd/sponsord.service +++ b/deploy/systemd/sponsord.service @@ -17,7 +17,13 @@ LoadCredential=treasury-password:/etc/sponsord/treasury-password LoadCredential=treasury-keystore.json:/etc/sponsord/treasury-keystore.json Environment=SPONSORD_API_TOKEN_FILE=%d/api-token Environment=SPONSORD_TREASURY_PASSWORD_FILE=%d/treasury-password -Environment=SPONSORD_TREASURY_KEYSTORE=%d/treasury-keystore.json +# Newer systemd (255+) loads credentials at 0440, and the daemon refuses a +# keystore its group can read. Copy it to a private 0600 file first; it is +# still the encrypted keystore, and the password stays in %d. +RuntimeDirectory=sponsord +RuntimeDirectoryMode=0700 +ExecStartPre=/usr/bin/install -m 0600 %d/treasury-keystore.json %t/sponsord/treasury-keystore.json +Environment=SPONSORD_TREASURY_KEYSTORE=%t/sponsord/treasury-keystore.json DynamicUser=yes Restart=on-failure RestartSec=5s diff --git a/docs/operator.md b/docs/operator.md index efa4c3e..dcd5c74 100644 --- a/docs/operator.md +++ b/docs/operator.md @@ -118,7 +118,9 @@ For a different kind of check, see [integrator.md](integrator.md). source with `docker compose build` ([`deploy/Dockerfile`](../deploy/Dockerfile)). - **systemd:** [`deploy/systemd/`](../deploy/systemd) has a unit for each, - with secrets as `LoadCredential=` credentials. + with secrets as `LoadCredential=` credentials. The daemon's unit copies the + keystore credential to a `0600` file under `/run/sponsord` before start, + since newer systemd loads credentials group-readable. - **Release binaries:** each `sponsord-vX.Y.Z` and `sponsord-onramp-vX.Y.Z` GitHub Release has its binary for Linux x86_64 and aarch64, with a signed `SHA256SUMS`. From e8b89e0e650af8d2d8bd963f70032136a22226b3 Mon Sep 17 00:00:00 2001 From: Ant Somers Date: Mon, 5 Oct 2026 13:33:04 +0300 Subject: [PATCH 2/2] docs: describe the credential mode trigger accurately in the unit and docs The 0440 mode comes from systemd 254+ on Linux 6.4+, where credentials live on tmpfs and access is granted through an ACL whose mask shows in the group bits; it is not systemd 255 specifically, nor literally group-readable. Say "some systemd versions (254+ on Linux 6.4+, seen on 255)", note that /run/sponsord is removed on stop, and tell operators with an installed copy of the unit which lines to take. Co-Authored-By: Claude Opus 5.5 --- crates/sponsord/CHANGELOG.md | 12 ++++++++---- deploy/systemd/sponsord.service | 8 +++++--- docs/operator.md | 7 ++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/sponsord/CHANGELOG.md b/crates/sponsord/CHANGELOG.md index 92ab4d9..4bd7611 100644 --- a/crates/sponsord/CHANGELOG.md +++ b/crates/sponsord/CHANGELOG.md @@ -43,7 +43,11 @@ release. ### Fixed -- **systemd unit on systemd 255.** `deploy/systemd/sponsord.service` copies - the keystore credential to a `0600` file under `/run/sponsord` before start. - systemd 255 loads credentials at `0440`, which the daemon refused as an - insecure keystore ([#36](https://github.com/decdn/sponsord/issues/36)). +- **Keystore credential in the systemd unit.** + `deploy/systemd/sponsord.service` copies the keystore credential to a `0600` + file under `/run/sponsord` before start. Some systemd versions (254+ on + Linux 6.4+, seen on 255) load credentials at `0440`, which the daemon + refused as an insecure keystore + ([#36](https://github.com/decdn/sponsord/issues/36)). If you installed a copy + of the unit, take its new `RuntimeDirectory=`, `RuntimeDirectoryMode=`, + `ExecStartPre=` and `SPONSORD_TREASURY_KEYSTORE` lines. diff --git a/deploy/systemd/sponsord.service b/deploy/systemd/sponsord.service index 1a607f8..454e3e0 100644 --- a/deploy/systemd/sponsord.service +++ b/deploy/systemd/sponsord.service @@ -17,9 +17,11 @@ LoadCredential=treasury-password:/etc/sponsord/treasury-password LoadCredential=treasury-keystore.json:/etc/sponsord/treasury-keystore.json Environment=SPONSORD_API_TOKEN_FILE=%d/api-token Environment=SPONSORD_TREASURY_PASSWORD_FILE=%d/treasury-password -# Newer systemd (255+) loads credentials at 0440, and the daemon refuses a -# keystore its group can read. Copy it to a private 0600 file first; it is -# still the encrypted keystore, and the password stays in %d. +# Some systemd versions (254+ on Linux 6.4+, seen on 255) load credentials at +# mode 0440, and the daemon refuses a keystore with any group or other bit +# set. So copy it to a private 0600 file first. The copy is still the +# encrypted keystore, the password stays in %d, and systemd removes +# /run/sponsord when the service stops. RuntimeDirectory=sponsord RuntimeDirectoryMode=0700 ExecStartPre=/usr/bin/install -m 0600 %d/treasury-keystore.json %t/sponsord/treasury-keystore.json diff --git a/docs/operator.md b/docs/operator.md index dcd5c74..97fb0ea 100644 --- a/docs/operator.md +++ b/docs/operator.md @@ -118,9 +118,10 @@ For a different kind of check, see [integrator.md](integrator.md). source with `docker compose build` ([`deploy/Dockerfile`](../deploy/Dockerfile)). - **systemd:** [`deploy/systemd/`](../deploy/systemd) has a unit for each, - with secrets as `LoadCredential=` credentials. The daemon's unit copies the - keystore credential to a `0600` file under `/run/sponsord` before start, - since newer systemd loads credentials group-readable. + with secrets as `LoadCredential=` credentials (systemd 252 or later). Some + systemd versions load credentials at mode `0440`, which the daemon refuses + for a keystore. So the daemon's unit copies the keystore to a `0600` file + under `/run/sponsord` before start. - **Release binaries:** each `sponsord-vX.Y.Z` and `sponsord-onramp-vX.Y.Z` GitHub Release has its binary for Linux x86_64 and aarch64, with a signed `SHA256SUMS`.