Previously the runner unconditionally invoked the passthrough DEK
resolver, which required a 32-byte key. Jobs configured with
encryption_enabled=false arrive with EncryptedDek=nil and the resolver
returned an 'expected 32-byte DEK, got 0' error, failing every run.
When EncryptedDek is empty the runner now skips the encrypt stage and
io.Copy()s the compressed stream straight into the upload pipe. The
encrypted_dek on BackupCompleted stays empty as well, matching the
server's expectation for an un-encrypted run.
MinIO (and stricter S3 endpoints) reject presigned PUTs sent with
chunked transfer-encoding, returning HTTP 411 'Length Required'. The
pipeline could not know the final encrypted size up-front so it
streamed the request body with ContentLength=-1.
Drain the encrypt stage into a temp file, then issue the PUT with an
explicit Content-Length. The dump → compress → encrypt goroutines
still overlap because the drain reads from the encrypt pipe; only the
upload itself is sequenced after encryption completes.
The Alpine sqlite3 binary refuses to open /dev/stdout when running as
a non-root uid in a container ('Error: cannot open "/dev/stdout"'),
which breaks every backup attempt. Switch the dump path to stage the
snapshot in a temp file, then stream that file through gzip into the
pipeline. Adds streamSideEffect to the test mockRunner so the existing
gzip-wrap test can simulate the sqlite3 process writing to its
destination path.
The Alpine sqlite3 binary dynamically links against libreadline.so.8;
without it the agent crashes at the validate stage of the pipeline
with 'Error loading shared library libreadline.so.8: No such file or
directory'.
Add the readline package to the runtime stage so sqlite3 starts
cleanly.
Two defects exposed by the first real-world deployment (Flw VPS):
- Env tags were BACKUP_* (no Y). Server + dashboard use BACKUPY_*.
- agentKeyPattern only matched bkpy_(live|test)_<32 alnum>; server's
generateAgentKey emits 64 lowercase hex chars. Accept both.
The .gitignore rule "state/" was unanchored, so git also ignored
apps/agent/internal/state/ — the BoltDB-backed queue persistence
package. CI build failed with:
internal/queue/queue.go:13:2: no required module provides package
github.com/backupy/backupy/apps/agent/internal/state
Anchored the rule to repo root (/state/, /var/) so it only matches
the runtime data directory, never a Go package.
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/ — Go agent (WSS client, persistent queue, Docker
discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/ — protobuf wire format (generated .pb.go committed
so the repo builds without protoc)
- docs/ — agent spec + wire-protocol contract
Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).