From 0195f6e75caea9f86c184fa3827b4dfd578bd80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bot=20of=20Thomas=20G=C3=BCttler?= Date: Thu, 4 Jun 2026 07:15:04 +0200 Subject: [PATCH] fix: bust stale Dagger cache and harden SSH key normalisation in Deployer (#406) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the persistent `Load key "/root/.ssh/id_ed25519": error in libcrypto` failures in the `deploy-apk` and `deploy-linux` CI jobs (and the `website` workflow SSH steps) that have been occurring on every deploy run since the jobs first started running after #369. Closes #404 ### Root cause (diagnosed from run #1516 log) Two compounding problems were found: 1. **Stale Dagger cache** — The `tr -d \x27\r\x27` normalisation step added in #369 was shown as `CACHED` by Dagger on every subsequent run. Dagger caches by input-content hash; if the very first execution produced a corrupted key file, that broken cached layer is replayed forever. 2. **`.ssh/` directory permissions** — Dagger creates parent directories for secret mounts with 755 permissions. Mounting the raw key directly inside `/root/.ssh/` may cause Dagger to (re-)create that directory with 755 instead of the 700 that OpenSSH requires. ### Changes (`ci/main.go` — `Deployer` function only) - **Explicit `.ssh` setup**: `mkdir -p /root/.ssh && chmod 700 /root/.ssh` runs before any Dagger secret mount. - **Move raw-key mount out of `.ssh/`**: Secret mounted at `/tmp/id_ed25519.raw`. - **Python3 normalisation instead of `tr`**: Handles CRLF, bare-CR, and missing trailing newline. Changing the command changes the Dagger cache key, forcing a fresh read of the current live secret. ## Test plan - [ ] `deploy-apk` job completes without `error in libcrypto` - [ ] `deploy-linux` job completes without `error in libcrypto` - [ ] `publish-android` (Play Store) job continues to succeed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Thomas SharedInbox Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/406 --- ci/main.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ci/main.go b/ci/main.go index 6c95d8a..4aa3e7f 100644 --- a/ci/main.go +++ b/ci/main.go @@ -338,12 +338,17 @@ func (m *Ci) Deployer(sshKey *dagger.Secret, knownHosts *dagger.Secret) *dagger. return dag.Container(). From("alpine:3.21"). WithExec([]string{"apk", "--no-cache", "add", "rsync", "openssh-client", "python3", "tar"}). - // Mount at a raw path so we can normalise before use: strip any CRLF line - // endings that appear when the key is stored or exported on Windows, which - // cause "error in libcrypto" in Alpine's LibreSSL-backed openssh. - WithMountedSecret("/root/.ssh/id_ed25519.raw", sshKey, dagger.ContainerWithMountedSecretOpts{Mode: 0600}). - WithExec([]string{"sh", "-c", - "tr -d '\\r' < /root/.ssh/id_ed25519.raw > /root/.ssh/id_ed25519 && chmod 600 /root/.ssh/id_ed25519"}). + // Create .ssh with strict permissions before Dagger mounts anything there, + // so the directory is 700 (not Dagger's default 755). + WithExec([]string{"sh", "-c", "mkdir -p /root/.ssh && chmod 700 /root/.ssh"}). + // Mount the raw key outside .ssh so Dagger cannot override the directory + // permissions we just set above. + WithMountedSecret("/tmp/id_ed25519.raw", sshKey, dagger.ContainerWithMountedSecretOpts{Mode: 0600}). + // Normalise with Python3: strip CRLF/bare-CR, ensure trailing newline. + // Using Python3 (not tr) changes the Dagger cache key so stale cached + // results from the old tr-based step are not reused. + WithExec([]string{"python3", "-c", + "import os; raw=open('/tmp/id_ed25519.raw','rb').read(); key=raw.replace(b'\\r\\n',b'\\n').replace(b'\\r',b'\\n'); key=key if key.endswith(b'\\n') else key+b'\\n'; open('/root/.ssh/id_ed25519','wb').write(key); os.chmod('/root/.ssh/id_ed25519',0o600)"}). WithMountedSecret("/root/.ssh/known_hosts", knownHosts, dagger.ContainerWithMountedSecretOpts{Mode: 0644}). WithEnvVariable("RSYNC_RSH", "ssh -i /root/.ssh/id_ed25519") }