Repository navigation
feat(cloud): optional account and light cloud backup (spec 27) - #216
Merged
Merged
Conversation
A new crate, botloft-cloud, with its own SQLite: POST /v1/login, the page the link opens (a button confirms, so mail scanners sign nobody in), the app's poll, /v1/me, logout and removing a device. Tokens are kept as SHA-256, sign-in is rate limited by e-mail and by address, and the e-mail goes through a Mailer (SMTP, or an outbox in tests). The spec (27.2-27.4) now says the button confirms, that the 60 s gap and bad addresses answer 429 and 400, and adds behind_proxy.
Only the crews' structure, the bots' memory and the routines go up; chats and files stay on the computer. A copy is a few MB, so the defaults drop to 50 MiB a copy, 200 MiB an account and 5 copies.
The server's disk is shared with other work, so the copies go to a bucket (R2) and the VPS keeps only cloud.db and a short-lived temp file per upload.
…ng an account (C3) PUT/GET/DELETE /v1/copies with size and SHA-256 checked while the upload lands in a temp file, Range downloads, a per-account quota that does not count the copy the new one pushes out, and the oldest copies dropped only after the new one is safe. Bytes go through CopyStore (a folder or an S3-compatible bucket, via object_store). Deleting an account asks by e-mail and a button deletes the rows and then the bytes.
backup.export takes a scope: light keeps the structure, each bot's memory and rules and the routines, and leaves out chats, runs, usage and files, in a folder of its own so a full export is never replaced. The new cloud module signs in by the e-mail link in the background, keeps the token in secrets/cloud.json, and sends, lists, brings down and deletes copies with SHA-256 checked on both ends, plus cloud.* methods, notifications and the fake for the app. Answered aside, because a transfer takes minutes. Tested against the real botloft-cloud server running in the test.
An Account block above the copy saved on this computer: ask for the e-mail link and wait for it, then send a light copy with its passphrase and a progress bar, list the copies in the cloud to restore or delete (with a confirmation), sign out, and delete the account by e-mail. Restoring a cloud copy reuses the restore flow, which now says the chats and files are not in a light copy. Texts in the three languages.
…database (C4) deploy/cloud has the Dockerfile, a compose file that only publishes on 127.0.0.1 and runs read-only without privileges, a systemd unit, a Caddyfile, an example cloud.toml (a test keeps it valid) and a script that backs the database up with rclone. The server gains GET /health, botloft-cloud backup (VACUUM INTO, safe while it runs) and a clean exit on SIGTERM. The guide is docs/cloud-deploy.md: bucket, e-mail, proxy, backup and restore, known limits. Checked with the real image: it builds, the container is healthy, /health is 200 and /v1/me is 401, the backup works while it runs, the database restores and the container stops in under a second.
… Coolify compose For a platform that keeps settings rather than files: the SMTP password and the bucket secret come from BOTLOFT_CLOUD_SMTP_PASSWORD and BOTLOFT_CLOUD_BUCKET_SECRET (the variable wins over password_file and secret_file), and the whole cloud.toml can be BOTLOFT_CLOUD_CONFIG. The secrets are never in an error or the log. deploy/cloud/docker-compose.coolify.yml runs the image without a published port or a file on the machine, and is plain Compose, so docker compose config accepts it. Checked with the real image: it loads from a docker save tarball and starts from that compose with only variables, /health is 200, /v1/me is 401, and without the secrets it refuses to start.
…loudflare lesson in the guide A scratch daemon signed in by the e-mail link, sent a light copy to the R2 bucket, brought it back, and an empty second daemon restored the crew, the bot, the routine and the memory from it. The VPS firewall only lets Cloudflare reach 80 and 443, so the server's record has to be proxied.
…dflare Behind Cloudflare the server saw the edge's address as the client, so the per-address sign-in limit was shared by everyone through that edge. With client_ip_header = "CF-Connecting-IP" the header that holds the real address wins over X-Forwarded-For; a value that is not an address is ignored, and the option is off by default because anyone could write the header if they could reach the server. The e-mail landing in the main inbox is recorded in spec 19.
The sign-in and delete-account e-mails go out as text and as HTML: a card with the flame, a title, a big button, the link written out for a client that does not draw the button, and a dark version. The pages behind the links are a card in the app's colors and type, light or dark as the device is, with a check, an alert or a red button as the case asks. The flame is the app icon without its black square, served at /brand/flame.png. The pages still run no script and may load the flame only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An optional Botloft account and a light cloud backup (spec 27). It is off until you press Sign in, and nothing is sent without a click.
crates/botloft-cloud): axum + SQLite. E-mail magic-link sign-in (no password), devices, quota, five copies kept per account, account deletion confirmed by e-mail. Copies live on disk or in an S3-compatible bucket (Cloudflare R2). Tokens are stored only as SHA-256, and rate limits are per e-mail and per address (client_ip_headerfor use behind Cloudflare). E-mails (text + HTML) and the link pages are drawn in Botloft's look, in en / pt-BR / es.cloud.*RPC methods (status, sign-in, upload, list, download, delete, delete account) with progress events, andbackup.export {scope: full | light}. The light copy holds the crews' structure, the bots' memory and the routines, without chats or files.docs/code-signing-policy.mdnow say so.[cloud] urldefaults tohttps://botloft.comitium.com.br; an empty value turns the account off, and a signed-out computer never touches the network.docs/cloud-deploy.md.Checked
cargo fmt,clippy -D warnings,cargo test --workspace(620 tests) andpnpm check(454 tests) pass; bindings are up to date.Manual steps for the reviewer