Skip to content

Commit a606d9d

Browse files
committed
docs(spec): account and cloud copies (27)
1 parent e2ff755 commit a606d9d

1 file changed

Lines changed: 102 additions & 1 deletion

File tree

‎docs/spec.md‎

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ path = "" # vazio = Microsoft Edge do Windows
170170
idle_minutes = 10
171171
max_open = 4
172172

173+
[cloud] # conta e cópias na nuvem (seção 27)
174+
url = "" # vazio = o servidor do Botloft; a conta só existe se o dono entrar
175+
173176
[tasks]
174177
max_hops = 4
175178
default_deadline_minutes = 120
@@ -775,6 +778,7 @@ Pragmas: `journal_mode=WAL`, `synchronous=NORMAL`, `foreign_keys=ON`, `busy_time
775778
- `log_level` vale só para os crates do Botloft; dependências ficam em `warn`, porque em `debug`/`trace` a pilha de WebSocket registra frames, que podem conter mensagens. `RUST_LOG` sobrepõe tudo e é só para depuração local.
776779
- Nome de anexo vira só o nome do arquivo (sem `..`, sem pasta, sem caracteres proibidos no Windows) antes de ir para o disco.
777780
- Ferramentas conectadas (25): só o dono cadastra, pelo app. Um servidor `stdio` roda com os poderes do dono, fora das cercas de 7.5, e um `http` recebe o que o bot mandar; o cadastro diz isso e pede confirmação. Segredos dos servidores ficam em `secrets\mcp` e no ambiente dos bots ligados, nunca no banco, no `mcp.json` ou no log.
781+
- Conta e cópias na nuvem (27): opcionais e só por ação do dono. O token do aparelho fica em `secrets\cloud.json` (ACL do usuário), o servidor guarda dele só o SHA-256, e a cópia sai selada com a senha do dono, que não vai ao servidor. O cliente só fala `https` (fora `127.0.0.1`). Não é telemetria: nada é enviado sem o clique do dono.
778782
- Isolamento entre bots é cooperativo (mesmo usuário do Windows). Documentar isso no README sem prometer sandbox.
779783
- Navegador dos bots (21): perfil próprio por bot, sem os logins do navegador do dono. A porta do DevTools escuta só em 127.0.0.1 e não tem senha: enquanto o navegador está aberto, outro programa do computador pode controlá-lo, como pode fazer com o resto do que roda com o usuário. Páginas não chegam a ela: o Chromium recusa WebSocket com `Origin` de site sem `--remote-allow-origins`. Trocar a porta por um pipe fica para depois. O texto das páginas pode trazer instruções para o bot; as tools dizem a ele que não valem como pedido do dono, e nos modos que perguntam, cada site novo passa pelo dono (21.5).
780784
- Pasta de trabalho escolhida (5): todos os bots da crew a editam como a própria pasta. Por isso ela não pode tocar a pasta de dados do Botloft (segredos, banco), conter os workspaces de todos os bots, encostar nas pastas de outra crew nem ser um disco inteiro.
@@ -819,7 +823,7 @@ Em fatias (18, item 8). O que já vale fora do Windows:
819823

820824
### 14.2 Cópia de segurança
821825

822-
Formatar o computador ou perder a pasta do Botloft não pode levar as equipes junto. O dono exporta tudo para um arquivo `.botloft`, protegido com uma senha dele, e importa esse arquivo em outro computador ou depois de reinstalar. É o mesmo arquivo que um backup na nuvem vai enviar: o servidor guardaria só bytes que não consegue ler.
826+
Formatar o computador ou perder a pasta do Botloft não pode levar as equipes junto. O dono exporta tudo para um arquivo `.botloft`, protegido com uma senha dele, e importa esse arquivo em outro computador ou depois de reinstalar. É o mesmo arquivo que a cópia na nuvem envia (27): o servidor guarda só bytes que não consegue ler.
823827

824828
- **O que vai:** uma cópia consistente do banco (`VACUUM INTO`): equipes, bots, conversas, rotinas, regras de "permitir sempre", acessos entre equipes e o uso. E a pasta de cada equipe em `workspaces_root`, arquivadas inclusive: a memória (`CLAUDE.md`) e as regras de cada bot, os anexos e a `shared`. Vai também um `manifest.json` com o formato (1), a data, a versão do Botloft e as equipes, com os nomes dos bots.
825829
- **O que fica:** o que o Botloft escreve de novo a cada início (`.botloft\` e `.claude\settings.json` de cada bot, 7.5); os segredos, os logs e os perfis dos navegadores dos bots, que estão na pasta de dados e não em `workspaces_root`; e as pastas de trabalho que o dono escolheu fora do Botloft (5), que são dele. O manifesto lista essas pastas, para a importação avisar. O login do Claude também é de cada computador e se faz de novo.
@@ -2089,3 +2093,100 @@ Finanças, jurídico e saúde, que pedem aviso de que não substituem um profiss
20892093
| **G2** Chefe | `list_bot_templates`, `get_bot_template`, `template` em `suggest_bot`, regras do chefe | tools com `FakeRuntime`: só o chefe, `id` desconhecido, junção de `instructions`, limite de 8 000; manual (PR): pedir ao chefe real "preciso de alguém para as redes sociais" e ver o cartão |
20902094
| **G3** App | a Agência de bots, "Saber mais", "Adicionar na equipe", convite na equipe só com o chefe, textos nos três idiomas | `FakeBotloft`; teste de que toda ficha tem texto nos três idiomas; `pnpm check`; a tela no preview. Manual (PR): criar cada um dos 12 com o Claude Code real e dar a cada um uma tarefa típica |
20912095
| **G4** Mais papéis | uma área por PR: Produto e gestão (14, feito), Marketing e vendas (18, feito), Engenharia (8, feito), Conteúdo, pesquisa e aprendizado (6, feito); categorias `product`, `marketing` e `learning`; chips só das categorias com papel; convite com 8 destaques e "Ver todos os bots" | `catalog_lint` e o teste de texto nos três idiomas em cada PR; manual (PR): 2 ou 3 papéis da área, os mais diferentes entre si, com o Claude Code real |
2096+
2097+
## 27. Conta e cópia na nuvem
2098+
2099+
### 27.1 O que é
2100+
2101+
A cópia de segurança de 14.2 já protege contra formatar o computador, mas só se o dono guardar o arquivo em outro lugar. Esta seção traz uma **conta** opcional e um lugar onde as cópias ficam guardadas: o dono envia a cópia selada pelo app e a baixa de volta em qualquer computador. É o primeiro passo para o celular (aprovar e responder de longe), que fica para depois (27.9).
2102+
2103+
Três promessas valem para tudo aqui:
2104+
2105+
- **É opcional e é do dono.** Sem conta, o Botloft não usa a rede para nada além do que já usa (o atualizador, os bots). Nada é enviado sem um clique do dono, e a primeira tela diz o que o servidor guarda.
2106+
- **O servidor não lê a cópia.** O arquivo `.botloft` sai selado com a senha da cópia (14.2) e assim chega ao servidor: ele guarda bytes que não consegue abrir. A **senha da conta** não existe (o login é por e-mail, 27.3) e a **senha da cópia** nunca sai do computador. Quem perde a senha da cópia perde as cópias da nuvem: o servidor não tem como recuperar.
2107+
- **O servidor é nosso e o código é aberto.** É uma VPS própria com um serviço Rust deste repositório (`botloft-cloud`, 27.2). Quem quiser pode rodar o seu e apontar o app para ele (`[cloud] url`, 6).
2108+
2109+
O servidor guarda só: o e-mail, os aparelhos que entraram (nome, data de entrada, último uso), e as cópias (tamanho, data, SHA-256 e os bytes selados). Não sabe nomes de equipes, de bots nem o que há dentro.
2110+
2111+
### 27.2 O servidor (`crates/botloft-cloud`)
2112+
2113+
Um binário, `botloft-cloud serve --config cloud.toml`, com `axum` e SQLite próprio (migrations do crate, `migrations/NNNN_nome.sql`, separado do banco do daemon). Fica atrás de um proxy com TLS (Caddy ou nginx): o cliente só fala `https`, a não ser em `127.0.0.1` (testes). O `cloud.toml`:
2114+
2115+
| Chave | Padrão | O que é |
2116+
|---|---|---|
2117+
| `listen` | `127.0.0.1:8787` | onde escuta, atrás do proxy |
2118+
| `public_url` | (obrigatória) | o endereço público, que vai nos links do e-mail |
2119+
| `data_dir` | `./data` | banco e cópias |
2120+
| `quota_bytes` | 2 GiB | o máximo por conta |
2121+
| `max_copy_bytes` | 1 GiB | o máximo de uma cópia |
2122+
| `keep` | 3 | quantas cópias por conta (a mais antiga sai quando entra a seguinte) |
2123+
| `smtp` | (obrigatória) | `host`, `port`, `user`, `password_file`, `from` |
2124+
2125+
O envio de e-mail passa por uma interface (`Mailer`): o serviço usa SMTP, e os testes uma caixa de saída na memória. Os textos do e-mail saem em `en`, `pt-BR` ou `es`, conforme o `locale` que o app mandou. Logs do servidor: nunca o e-mail, o token nem o código em nível `info`; o proxy guarda o IP por 7 dias para frear abuso e depois apaga. Sem telemetria.
2126+
2127+
### 27.3 Entrar (link mágico)
2128+
2129+
Sem senha. O fluxo é o de "entrar em outro aparelho", que não precisa de endereço do app para o link voltar:
2130+
2131+
1. O app chama `POST /v1/login {email, device_name, locale}`. A resposta é sempre `202 {request, wait}` (`request` é um segredo de 32 bytes em base64 url-safe; `wait` é 600 s), exista ou não a conta: ninguém descobre por aqui quem tem conta. O servidor guarda só o SHA-256 do `request`.
2132+
2. O e-mail leva um link `<public_url>/v1/login/confirm?code=<outro segredo>`, de uso único e válido por 10 minutos. Abri-lo, em qualquer aparelho, mostra "Pode voltar ao Botloft" e marca o pedido como aprovado. A conta nasce nesse momento, se for a primeira vez.
2133+
3. O app consulta `GET /v1/login/<request>` a cada 2 s: `{status: "pending"}`, `"expired"` ou `"approved"`. Na primeira consulta depois da aprovação vem `{status: "approved", token, device, email}` e o pedido deixa de existir. Quem só tem o link não tem o `request`, e quem o intercepta não tem o link.
2134+
4. O `token` (32 bytes) identifica o aparelho em todas as outras chamadas (`Authorization: Bearer`). O servidor guarda só o SHA-256, como o Botloft faz com os tokens dos bots (13). Fica no computador em `secrets\cloud.json` (ACL do usuário, 5 e 13).
2135+
2136+
Freios: 5 pedidos por hora por e-mail e 20 por hora por IP (`429` com `retry_after`); o e-mail não é enviado de novo se o último saiu há menos de 60 s. `POST /v1/logout` apaga o token deste aparelho; `GET /v1/me` devolve `{email, used, quota, devices}`; `DELETE /v1/devices/<id>` tira outro aparelho. **Apagar a conta:** `POST /v1/account/delete` manda um link ao e-mail e, confirmado, apaga a conta, os aparelhos e todas as cópias de uma vez; `DELETE /v1/account` não existe sem essa confirmação.
2137+
2138+
### 27.4 As cópias
2139+
2140+
- `PUT /v1/copies` envia o arquivo `.botloft` inteiro, em fluxo, com `Content-Length` e `X-Botloft-Sha256`. O servidor grava num arquivo temporário, confere o tamanho e o hash enquanto grava e só então o torna a cópia mais nova; falha, corte ou hash diferente não deixam nada. Responde `201 {id, size, created}`. Recusa com `413` e `reason` `too_big` (acima de `max_copy_bytes`) ou `quota` (a conta não cabe), `400` `bad_hash`. Depois de gravar uma cópia nova e só então, apaga as mais antigas além de `keep`.
2141+
- `GET /v1/copies` lista `{id, size, created}`, da mais nova para a mais antiga. `GET /v1/copies/<id>` baixa, com `Range` para retomar. `DELETE /v1/copies/<id>` apaga uma.
2142+
- O servidor não abre o arquivo: não confere o lacre nem o manifesto. Se o cliente enviou lixo, a restauração falha no app com `not_a_backup` (14.2), sem mudar nada.
2143+
- Erros vêm como `{reason, message}`; os `reason` são `unauthorized`, `not_found`, `rate_limited`, `login_expired`, `too_big`, `quota`, `bad_hash`.
2144+
2145+
### 27.5 No daemon (`cloud`)
2146+
2147+
Só o daemon fala com o servidor; o app nunca vê o token. O cliente (`reqwest` com `rustls`) lê `[cloud] url` do `config.toml` (6) e recusa `http` fora de `127.0.0.1`. Métodos novos do protocolo (11), todos só do dono:
2148+
2149+
| Método | O que faz |
2150+
|---|---|
2151+
| `cloud.status` | `{url, signedIn, email?, used?, quota?, pending?}` |
2152+
| `cloud.signin {email}` | começa o pedido (27.3) e devolve `{wait}`; o resto acontece em segundo plano |
2153+
| `cloud.signin_cancel` | desiste do pedido |
2154+
| `cloud.signout` | `POST /v1/logout` (se não conseguir, apaga o token local do mesmo jeito) |
2155+
| `cloud.upload {passphrase}` | `backup.export` (14.2) e envia o arquivo; devolve `{id, size, created}` |
2156+
| `cloud.copies` | a lista de 27.4 |
2157+
| `cloud.download {id}` | baixa para `<home>\restore\downloaded.botloft` e devolve o caminho; o app segue com `backup.stage {path, passphrase}` e `backup.confirm`, como numa cópia de arquivo |
2158+
| `cloud.delete {id}` | apaga uma cópia |
2159+
| `cloud.delete_account` | pede o link de apagar a conta (27.3) |
2160+
2161+
Notificações: `cloud.signed_in {email}`, `cloud.progress {sent, total}` (envio e download) e `cloud.signin_expired`. Erros que o dono resolve levam `reason` (20.8): os de 27.4 mais `offline` e `short_passphrase`. O envio de uma cópia que falhou recomeça do início (retomar o envio fica para depois). Nunca se loga e-mail, token nem endereço de cópia em `info`.
2162+
2163+
### 27.6 No app
2164+
2165+
Configurações, "Cópia de segurança" (14.2) ganha o bloco **Conta**, acima de "Salvar uma cópia":
2166+
2167+
- **Sem conta:** uma frase do que ela guarda ("seu e-mail e as cópias, que ninguém consegue abrir sem a sua senha"), o campo de e-mail e "Entrar". Depois: "Enviamos um link para ana@x.com. Abra o e-mail e volte aqui." com "Reenviar" (acende após 60 s) e "Cancelar".
2168+
- **Com conta:** o e-mail, "Sair", o espaço usado ("320 MB de 2 GB"), "Enviar uma cópia agora" (a senha duas vezes, as mesmas regras de 14.2, uma barra de progresso) e a lista "Cópias na nuvem" (data, tamanho), cada uma com "Restaurar" (pede a "Senha da cópia", baixa e entra no mesmo passo de confirmação de 14.2) e "Apagar". "Apagar minha conta" no fim, com a confirmação que diz que apaga todas as cópias.
2169+
- Textos nos três idiomas, sem jargão (15.6): "nuvem", "conta", "cópia"; nunca "servidor", "token" ou "daemon".
2170+
2171+
### 27.7 Privacidade (13)
2172+
2173+
- Nada vai à rede sem o dono pedir; sair da conta apaga o token do computador.
2174+
- O servidor vê o e-mail, o IP de quem conecta (no proxy, por 7 dias), os tamanhos e as datas. Não vê a senha da cópia nem o que há na cópia.
2175+
- Quem controla o servidor pode apagar ou recusar cópias, e pode ver quando e quanto se envia. Por isso o lacre é do dono e não do servidor.
2176+
- Quem acessa o e-mail entra na conta e baixa as cópias seladas, mas não as abre sem a senha da cópia.
2177+
2178+
### 27.8 Marcos
2179+
2180+
| Marco | O que entra | Teste |
2181+
|---|---|---|
2182+
| **C1** Spec | esta seção | revisão do dono |
2183+
| **C2** Servidor: conta | crate `botloft-cloud`, migrations, `Mailer`, login por link, aparelhos, freios | unidade; fluxo completo com a caixa de saída; mesma resposta para e-mail novo e conhecido; link usado ou vencido |
2184+
| **C3** Servidor: cópias | `PUT`/`GET`/`DELETE`, hash, cota, `keep`, `Range`, apagar a conta | corte no meio não deixa nada; hash errado; cota; a mais antiga só sai depois da nova |
2185+
| **C4** Hospedagem | `Dockerfile`, serviço systemd, `Caddyfile` e `docs/cloud-deploy.md`; o crate entra na CI | build; subir e responder em `/v1/me` com 401 |
2186+
| **C5** Daemon | cliente `cloud`, métodos e notificações do protocolo, `secrets\cloud.json`, tipos gerados | o servidor de C2 e C3 no processo, em `127.0.0.1`: entrar, enviar, listar, baixar, restaurar em outra pasta |
2187+
| **C6** App | o bloco Conta, `FakeBotloft`, três idiomas | `pnpm check`; a tela no preview |
2188+
| **C7** Teste real | volta completa com a VPS de verdade e uma instalação de teste: e-mail chegando (entrega, spam), envio, restauração em pasta limpa | registrado na seção 19 |
2189+
2190+
### 27.9 Fora desta etapa
2191+
2192+
Envio automático (pede guardar a senha da cópia no computador, decisão à parte), retomar um envio, cópias só do que mudou, plano pago e cotas por plano, login por Google ou GitHub, recuperar uma senha de cópia perdida, trocar o servidor sem perder as cópias, e o celular: o relay pelo servidor, o pareamento por QR e o app com aprovações e perguntas, que usam esta conta e vêm em seguida.

0 commit comments

Comments
 (0)