ekodide 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ekodide-0.1.0/LICENSE +21 -0
- ekodide-0.1.0/PKG-INFO +258 -0
- ekodide-0.1.0/README.md +227 -0
- ekodide-0.1.0/ekodide/__init__.py +56 -0
- ekodide-0.1.0/ekodide/acervo.py +84 -0
- ekodide-0.1.0/ekodide/buscador.py +102 -0
- ekodide-0.1.0/ekodide/caixa_postal.py +136 -0
- ekodide-0.1.0/ekodide/carteiro.py +216 -0
- ekodide-0.1.0/ekodide/cli.py +391 -0
- ekodide-0.1.0/ekodide/cofre.py +44 -0
- ekodide-0.1.0/ekodide/config.py +84 -0
- ekodide-0.1.0/ekodide/cortina.py +155 -0
- ekodide-0.1.0/ekodide/frase.py +48 -0
- ekodide-0.1.0/ekodide/lacre.py +85 -0
- ekodide-0.1.0/ekodide/recebedor.py +186 -0
- ekodide-0.1.0/ekodide/vizinhanca.py +113 -0
- ekodide-0.1.0/ekodide.egg-info/PKG-INFO +258 -0
- ekodide-0.1.0/ekodide.egg-info/SOURCES.txt +33 -0
- ekodide-0.1.0/ekodide.egg-info/dependency_links.txt +1 -0
- ekodide-0.1.0/ekodide.egg-info/entry_points.txt +2 -0
- ekodide-0.1.0/ekodide.egg-info/requires.txt +1 -0
- ekodide-0.1.0/ekodide.egg-info/top_level.txt +1 -0
- ekodide-0.1.0/pyproject.toml +46 -0
- ekodide-0.1.0/setup.cfg +4 -0
- ekodide-0.1.0/tests/test_acervo.py +81 -0
- ekodide-0.1.0/tests/test_caixa_postal.py +55 -0
- ekodide-0.1.0/tests/test_cli.py +127 -0
- ekodide-0.1.0/tests/test_cofre.py +38 -0
- ekodide-0.1.0/tests/test_config.py +51 -0
- ekodide-0.1.0/tests/test_cortina.py +59 -0
- ekodide-0.1.0/tests/test_frase.py +28 -0
- ekodide-0.1.0/tests/test_lacre.py +47 -0
- ekodide-0.1.0/tests/test_puxar.py +158 -0
- ekodide-0.1.0/tests/test_vizinhanca.py +51 -0
- ekodide-0.1.0/tests/test_voo.py +152 -0
ekodide-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matheus Gustav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ekodide-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ekodide
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Enviar e receber arquivos pela rede, lacrados (HMAC) e idênticos. Sem dependências.
|
|
5
|
+
Author: Matheus Gustav
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MatheusGustav/ekodide
|
|
8
|
+
Project-URL: Repository, https://github.com/MatheusGustav/ekodide
|
|
9
|
+
Project-URL: Issues, https://github.com/MatheusGustav/ekodide/issues
|
|
10
|
+
Keywords: file-transfer,hmac,aes-gcm,encryption,lan,wifi,p2p
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Communications :: File Sharing
|
|
23
|
+
Classifier: Topic :: System :: Networking
|
|
24
|
+
Classifier: Topic :: Security :: Cryptography
|
|
25
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: cryptography>=42
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Ekodide 🦜
|
|
33
|
+
|
|
34
|
+
Envia e recebe arquivos pela rede, **lacrados** (HMAC) e **idênticos** — sem
|
|
35
|
+
dependências (só a biblioteca padrão do Python).
|
|
36
|
+
|
|
37
|
+
O nome é de um papagaio africano (*odídẹ*): **repete com perfeição** (o arquivo
|
|
38
|
+
chega cópia exata, sha256 idêntico) e **voa** (vai de um aparelho a outro pela
|
|
39
|
+
rede, sem cabo). É código determinístico — não tem IA dentro. Algo *aciona* o
|
|
40
|
+
Ekodide (um humano no terminal, um script, um agente); o trabalho é deste
|
|
41
|
+
maquinário fixo.
|
|
42
|
+
|
|
43
|
+
## Instalar
|
|
44
|
+
|
|
45
|
+
Precisa só de **Python** (é zero-dependência, então `pipx` é opcional):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# direto do GitHub, isolado e no PATH (recomendado):
|
|
49
|
+
pipx install git+https://github.com/MatheusGustav/ekodide.git
|
|
50
|
+
|
|
51
|
+
# sem pipx — pip basta (cai em ~/.local/bin):
|
|
52
|
+
pip install --user git+https://github.com/MatheusGustav/ekodide.git
|
|
53
|
+
|
|
54
|
+
# pra desenvolver localmente:
|
|
55
|
+
git clone https://github.com/MatheusGustav/ekodide.git
|
|
56
|
+
pip install -e ekodide
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## A ideia central: sempre há 2 pontas
|
|
60
|
+
|
|
61
|
+
Toda transferência tem **quem recebe** e **quem envia**:
|
|
62
|
+
|
|
63
|
+
- **Quem RECEBE** deixa a caixa de correio aberta → `ekodide serve` (fica escutando).
|
|
64
|
+
- **Quem ENVIA** joga a carta → `ekodide send`.
|
|
65
|
+
|
|
66
|
+
### Início rápido (sem digitar IP nem inventar senha) ⭐
|
|
67
|
+
|
|
68
|
+
Quem recebe abre a caixa **na rede** (já se anuncia sozinho pros outros acharem):
|
|
69
|
+
```bash
|
|
70
|
+
ekodide serve --host 0.0.0.0
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Pareie o segredo **uma vez** — num aparelho gere a frase-código, no outro digite a mesma:
|
|
74
|
+
```bash
|
|
75
|
+
# aparelho A:
|
|
76
|
+
ekodide pair # mostra algo como: ekodide pair casa-vento-rio-azul-pedra-lobo
|
|
77
|
+
# aparelho B (digite a MESMA frase que apareceu em A):
|
|
78
|
+
ekodide pair casa-vento-rio-azul-pedra-lobo
|
|
79
|
+
```
|
|
80
|
+
A frase **é** o segredo — passe pela tela/voz, ela nunca trafega pela rede.
|
|
81
|
+
|
|
82
|
+
Veja quem está disponível e envie **pelo nome** (o IP é descoberto sozinho, mesmo
|
|
83
|
+
que mude por DHCP):
|
|
84
|
+
```bash
|
|
85
|
+
ekodide devices # lista os aparelhos na rede
|
|
86
|
+
ekodide send foto.jpg --para celular-matheus
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Cadastrar um apelido fixo (escolhendo da rede)
|
|
90
|
+
|
|
91
|
+
Se preferir um apelido fixo (em vez de resolver pelo nome toda vez), cadastre **sem
|
|
92
|
+
digitar IP** — o Ekodide lista quem está na rede e você só **escolhe qual é**:
|
|
93
|
+
```bash
|
|
94
|
+
ekodide config destino celular
|
|
95
|
+
# Procurando aparelhos na rede pra cadastrar 'celular'…
|
|
96
|
+
# 1) galaxy-do-mat http://192.168.0.9:8778
|
|
97
|
+
# 2) notebook-sala http://192.168.0.20:8778
|
|
98
|
+
# Qual é o aparelho? [número]: 1
|
|
99
|
+
# Destino 'celular' = http://192.168.0.9:8778
|
|
100
|
+
ekodide send foto.jpg --para celular # daí em diante, só o apelido
|
|
101
|
+
```
|
|
102
|
+
Só aparece quem está com a **caixa aberta** (`ekodide serve`) — que é justamente quem
|
|
103
|
+
pode receber. Se a lista vier vazia, é sinal de abrir a caixa no outro aparelho.
|
|
104
|
+
|
|
105
|
+
### Configurar na mão (avançado / scripts)
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
ekodide config segredo "uma-chave-bem-secreta" # IGUAL nos dois aparelhos
|
|
109
|
+
ekodide config destino pc 192.168.0.10 # IP cru (completa http+porta) ou URL inteira
|
|
110
|
+
ekodide config nome meu-pc # como apareço no 'devices'
|
|
111
|
+
ekodide config show # confere (segredo mascarado)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Os comandos
|
|
115
|
+
|
|
116
|
+
### `send` — enviar
|
|
117
|
+
```bash
|
|
118
|
+
ekodide send arquivo.pdf --para celular # um arquivo
|
|
119
|
+
ekodide send ~/projeto --para pc # uma PASTA inteira (com subpastas)
|
|
120
|
+
ekodide send video.mp4 --para celular # arquivo grande? pica e remonta sozinho
|
|
121
|
+
ekodide send foto.jpg --para pc -m "print do erro" # -m: etiqueta pro histórico
|
|
122
|
+
ekodide send foto.jpg --para pc --descobrir # acha o IP na rede (ignora a config)
|
|
123
|
+
```
|
|
124
|
+
`--para` usa o **apelido** do destino. Se ele estiver na config, usa o IP de lá;
|
|
125
|
+
senão (ou com `--descobrir`), acha o aparelho **pelo nome na rede**. O caminho é
|
|
126
|
+
como no git: relativo à pasta atual.
|
|
127
|
+
|
|
128
|
+
### `devices` — quem está na rede
|
|
129
|
+
```bash
|
|
130
|
+
ekodide devices # lista os aparelhos Ekodide que estão com a caixa aberta
|
|
131
|
+
ekodide devices --tempo 4 # escuta por mais tempo (padrão: 2.5s)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `pair` — combinar o segredo (sem inventar/digitar chave aleatória)
|
|
135
|
+
```bash
|
|
136
|
+
ekodide pair # GERA uma frase-código, guarda e mostra pra ditar no outro
|
|
137
|
+
ekodide pair casa-vento-rio-azul-pedra-lobo # RECEBE a frase ditada pelo outro aparelho
|
|
138
|
+
ekodide pair --palavras 8 # frase mais longa (mais forte) ao gerar
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `firewall` — liberar a entrada (no lado que recebe)
|
|
142
|
+
```bash
|
|
143
|
+
ekodide firewall # detecta o firewall, diz o que falta e mostra o comando
|
|
144
|
+
ekodide firewall --abrir # executa pra liberar (você autoriza)
|
|
145
|
+
```
|
|
146
|
+
Sabe lidar com **Linux** (firewalld/ufw — por porta, pede sudo), **Windows** (netsh —
|
|
147
|
+
por porta, precisa de um Prompt de Administrador) e **macOS** (Application Firewall —
|
|
148
|
+
é por **aplicativo**, libera o Python; e costuma vir desligado, aí nem precisa).
|
|
149
|
+
|
|
150
|
+
### `serve` — receber (abrir a caixa)
|
|
151
|
+
```bash
|
|
152
|
+
ekodide serve # escuta e grava o que chegar (padrão: ~/Downloads)
|
|
153
|
+
ekodide serve --host 0.0.0.0 # abre na LAN (pra receber de outro aparelho)
|
|
154
|
+
ekodide serve --dir ~/Recebidos # escolhe a pasta destino
|
|
155
|
+
ekodide serve --compartilhar ~/Publica # deixa o outro lado PUXAR dessa pasta (ver abaixo)
|
|
156
|
+
```
|
|
157
|
+
Deixe rodando num terminal; `Ctrl+C` para parar.
|
|
158
|
+
|
|
159
|
+
### `list` / `pull` — puxar de outro aparelho
|
|
160
|
+
|
|
161
|
+
O contrário do `send`: em vez de empurrar, você **puxa** um arquivo que o outro lado
|
|
162
|
+
deixou disponível. O outro precisa estar servindo **com a pasta compartilhada**
|
|
163
|
+
(`ekodide serve --compartilhar <pasta>`); sem isso, nada é exposto pra leitura.
|
|
164
|
+
```bash
|
|
165
|
+
ekodide list --de pc # vê o que o 'pc' compartilha pra puxar
|
|
166
|
+
ekodide pull relatorio.pdf --de pc # puxa o arquivo pra cá (padrão: ~/Downloads)
|
|
167
|
+
ekodide pull Fotos/foto.jpg --de pc --dir ~/Recebidos # de subpasta, salvando onde quiser
|
|
168
|
+
```
|
|
169
|
+
Puxar é **opt-in**: só funciona contra quem serviu com `--compartilhar`. O conteúdo
|
|
170
|
+
volta cifrado (cofre) e lacrado (HMAC), e a pasta compartilhada é **cercada** — um
|
|
171
|
+
pedido com `../` nunca escapa dela.
|
|
172
|
+
|
|
173
|
+
### `config` — ajustar
|
|
174
|
+
```bash
|
|
175
|
+
ekodide config show # ver segredo (mascarado) e destinos
|
|
176
|
+
ekodide config destino pc http://IP:8778 # cadastrar/atualizar um destino
|
|
177
|
+
ekodide config segredo "a-chave" # trocar o segredo
|
|
178
|
+
```
|
|
179
|
+
A config fica em `~/.config/ekodide/config.json` (cadeado `600`, porque tem o segredo).
|
|
180
|
+
|
|
181
|
+
## Receitas
|
|
182
|
+
|
|
183
|
+
**📤 PC → celular** (com o celular já escutando):
|
|
184
|
+
```bash
|
|
185
|
+
# no PC:
|
|
186
|
+
ekodide send relatorio.pdf --para celular
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**📥 celular → PC** (abra a caixa do PC primeiro):
|
|
190
|
+
```bash
|
|
191
|
+
# no PC (deixe rodando):
|
|
192
|
+
ekodide serve --host 0.0.0.0
|
|
193
|
+
# no outro aparelho:
|
|
194
|
+
ekodide send foto.jpg --para pc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 3 pegadinhas
|
|
198
|
+
|
|
199
|
+
1. **A caixa precisa estar aberta:** o lado que recebe tem que estar com
|
|
200
|
+
`ekodide serve` no ar.
|
|
201
|
+
2. **Mesmo segredo nos dois lados** — é a chave do cadeado.
|
|
202
|
+
3. **Firewall:** quem recebe precisa liberar **TCP 8778** (transferência) e **UDP 8779**
|
|
203
|
+
(descoberta). O jeito fácil: **`ekodide firewall --abrir`** (detecta firewalld/ufw e
|
|
204
|
+
roda com sudo). Na mão (Fedora): `sudo firewall-cmd --add-port=8778/tcp
|
|
205
|
+
--add-port=8779/udp --permanent && sudo systemctl restart firewalld`. Sintoma de
|
|
206
|
+
porta fechada: `No route to host` no envio (ou ninguém aparece no `devices`).
|
|
207
|
+
|
|
208
|
+
## Usar como biblioteca
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from pathlib import Path
|
|
212
|
+
from ekodide import enviar, servir
|
|
213
|
+
|
|
214
|
+
r = enviar(Path("foto.png"), "http://192.168.0.10:8778", segredo="...")
|
|
215
|
+
print(r.ok, r.destino)
|
|
216
|
+
|
|
217
|
+
# na outra ponta:
|
|
218
|
+
servir(Path("~/Downloads").expanduser(), segredo="...", host="0.0.0.0")
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Android
|
|
222
|
+
|
|
223
|
+
Em breve via **app nativo** (o celular como ponta passiva que o PC comanda).
|
|
224
|
+
Em desenvolvimento.
|
|
225
|
+
|
|
226
|
+
## Como é por dentro
|
|
227
|
+
|
|
228
|
+
| cômodo | papel |
|
|
229
|
+
|---|---|
|
|
230
|
+
| `lacre.py` | fechadura HMAC — o segredo nunca trafega |
|
|
231
|
+
| `cofre.py` | cifra o conteúdo (AES-256-GCM) — embaralhado na rede, idêntico no destino |
|
|
232
|
+
| `carteiro.py` | envia arquivo/pasta; arquivo grande vai **picado** em pedaços |
|
|
233
|
+
| `caixa_postal.py` | grava cercado (sem travessia, sem sobrescrever) e remonta os pedaços |
|
|
234
|
+
| `acervo.py` | LÊ cercado a pasta compartilhada pro "puxar" (sem travessia, sem fuga por symlink) |
|
|
235
|
+
| `buscador.py` | PUXA arquivo de outra ponta (pede, decifra, grava reusando a caixa postal) |
|
|
236
|
+
| `recebedor.py` | servidor HTTP leve que escuta e grava (e expõe /listar e /buscar pro puxar) |
|
|
237
|
+
| `vizinhanca.py` | descoberta na LAN: anuncia presença e acha aparelhos pelo nome (sem IP) |
|
|
238
|
+
| `frase.py` | gera o segredo como frase-código digitável (pareamento out-of-band) |
|
|
239
|
+
| `cortina.py` | detecta o firewall e monta/roda o comando pra liberar as portas |
|
|
240
|
+
| `config.py` | lê/grava `~/.config/ekodide/config.json` (segredo + destinos + nome) |
|
|
241
|
+
| `cli.py` | o comando `ekodide` (send/serve/list/pull/devices/pair/firewall/config) |
|
|
242
|
+
|
|
243
|
+
## Segurança (honesto)
|
|
244
|
+
|
|
245
|
+
Duas camadas, ambas chaveadas pelo segredo que as pontas compartilham (a frase-código
|
|
246
|
+
do pareamento — nunca trafega):
|
|
247
|
+
|
|
248
|
+
- **Lacre (HMAC-SHA256):** prova *quem* mandou, que ninguém mexeu no caminho, e que a
|
|
249
|
+
mensagem é recente (janela de 5 min).
|
|
250
|
+
- **Cofre (AES-256-GCM):** **cifra o conteúdo** — na rede passa só embaralhado; só
|
|
251
|
+
remetente e destinatário leem. O arquivo gravado fica byte-idêntico ao original.
|
|
252
|
+
|
|
253
|
+
Ainda é **mesma rede (Wi-Fi)** por foco, não por limite de cifra. O que falta pra
|
|
254
|
+
"rua" (internet) é endereçamento/NAT, não proteção do conteúdo.
|
|
255
|
+
|
|
256
|
+
## Licença
|
|
257
|
+
|
|
258
|
+
MIT — veja [LICENSE](LICENSE).
|
ekodide-0.1.0/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Ekodide 🦜
|
|
2
|
+
|
|
3
|
+
Envia e recebe arquivos pela rede, **lacrados** (HMAC) e **idênticos** — sem
|
|
4
|
+
dependências (só a biblioteca padrão do Python).
|
|
5
|
+
|
|
6
|
+
O nome é de um papagaio africano (*odídẹ*): **repete com perfeição** (o arquivo
|
|
7
|
+
chega cópia exata, sha256 idêntico) e **voa** (vai de um aparelho a outro pela
|
|
8
|
+
rede, sem cabo). É código determinístico — não tem IA dentro. Algo *aciona* o
|
|
9
|
+
Ekodide (um humano no terminal, um script, um agente); o trabalho é deste
|
|
10
|
+
maquinário fixo.
|
|
11
|
+
|
|
12
|
+
## Instalar
|
|
13
|
+
|
|
14
|
+
Precisa só de **Python** (é zero-dependência, então `pipx` é opcional):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# direto do GitHub, isolado e no PATH (recomendado):
|
|
18
|
+
pipx install git+https://github.com/MatheusGustav/ekodide.git
|
|
19
|
+
|
|
20
|
+
# sem pipx — pip basta (cai em ~/.local/bin):
|
|
21
|
+
pip install --user git+https://github.com/MatheusGustav/ekodide.git
|
|
22
|
+
|
|
23
|
+
# pra desenvolver localmente:
|
|
24
|
+
git clone https://github.com/MatheusGustav/ekodide.git
|
|
25
|
+
pip install -e ekodide
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## A ideia central: sempre há 2 pontas
|
|
29
|
+
|
|
30
|
+
Toda transferência tem **quem recebe** e **quem envia**:
|
|
31
|
+
|
|
32
|
+
- **Quem RECEBE** deixa a caixa de correio aberta → `ekodide serve` (fica escutando).
|
|
33
|
+
- **Quem ENVIA** joga a carta → `ekodide send`.
|
|
34
|
+
|
|
35
|
+
### Início rápido (sem digitar IP nem inventar senha) ⭐
|
|
36
|
+
|
|
37
|
+
Quem recebe abre a caixa **na rede** (já se anuncia sozinho pros outros acharem):
|
|
38
|
+
```bash
|
|
39
|
+
ekodide serve --host 0.0.0.0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Pareie o segredo **uma vez** — num aparelho gere a frase-código, no outro digite a mesma:
|
|
43
|
+
```bash
|
|
44
|
+
# aparelho A:
|
|
45
|
+
ekodide pair # mostra algo como: ekodide pair casa-vento-rio-azul-pedra-lobo
|
|
46
|
+
# aparelho B (digite a MESMA frase que apareceu em A):
|
|
47
|
+
ekodide pair casa-vento-rio-azul-pedra-lobo
|
|
48
|
+
```
|
|
49
|
+
A frase **é** o segredo — passe pela tela/voz, ela nunca trafega pela rede.
|
|
50
|
+
|
|
51
|
+
Veja quem está disponível e envie **pelo nome** (o IP é descoberto sozinho, mesmo
|
|
52
|
+
que mude por DHCP):
|
|
53
|
+
```bash
|
|
54
|
+
ekodide devices # lista os aparelhos na rede
|
|
55
|
+
ekodide send foto.jpg --para celular-matheus
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Cadastrar um apelido fixo (escolhendo da rede)
|
|
59
|
+
|
|
60
|
+
Se preferir um apelido fixo (em vez de resolver pelo nome toda vez), cadastre **sem
|
|
61
|
+
digitar IP** — o Ekodide lista quem está na rede e você só **escolhe qual é**:
|
|
62
|
+
```bash
|
|
63
|
+
ekodide config destino celular
|
|
64
|
+
# Procurando aparelhos na rede pra cadastrar 'celular'…
|
|
65
|
+
# 1) galaxy-do-mat http://192.168.0.9:8778
|
|
66
|
+
# 2) notebook-sala http://192.168.0.20:8778
|
|
67
|
+
# Qual é o aparelho? [número]: 1
|
|
68
|
+
# Destino 'celular' = http://192.168.0.9:8778
|
|
69
|
+
ekodide send foto.jpg --para celular # daí em diante, só o apelido
|
|
70
|
+
```
|
|
71
|
+
Só aparece quem está com a **caixa aberta** (`ekodide serve`) — que é justamente quem
|
|
72
|
+
pode receber. Se a lista vier vazia, é sinal de abrir a caixa no outro aparelho.
|
|
73
|
+
|
|
74
|
+
### Configurar na mão (avançado / scripts)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ekodide config segredo "uma-chave-bem-secreta" # IGUAL nos dois aparelhos
|
|
78
|
+
ekodide config destino pc 192.168.0.10 # IP cru (completa http+porta) ou URL inteira
|
|
79
|
+
ekodide config nome meu-pc # como apareço no 'devices'
|
|
80
|
+
ekodide config show # confere (segredo mascarado)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Os comandos
|
|
84
|
+
|
|
85
|
+
### `send` — enviar
|
|
86
|
+
```bash
|
|
87
|
+
ekodide send arquivo.pdf --para celular # um arquivo
|
|
88
|
+
ekodide send ~/projeto --para pc # uma PASTA inteira (com subpastas)
|
|
89
|
+
ekodide send video.mp4 --para celular # arquivo grande? pica e remonta sozinho
|
|
90
|
+
ekodide send foto.jpg --para pc -m "print do erro" # -m: etiqueta pro histórico
|
|
91
|
+
ekodide send foto.jpg --para pc --descobrir # acha o IP na rede (ignora a config)
|
|
92
|
+
```
|
|
93
|
+
`--para` usa o **apelido** do destino. Se ele estiver na config, usa o IP de lá;
|
|
94
|
+
senão (ou com `--descobrir`), acha o aparelho **pelo nome na rede**. O caminho é
|
|
95
|
+
como no git: relativo à pasta atual.
|
|
96
|
+
|
|
97
|
+
### `devices` — quem está na rede
|
|
98
|
+
```bash
|
|
99
|
+
ekodide devices # lista os aparelhos Ekodide que estão com a caixa aberta
|
|
100
|
+
ekodide devices --tempo 4 # escuta por mais tempo (padrão: 2.5s)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `pair` — combinar o segredo (sem inventar/digitar chave aleatória)
|
|
104
|
+
```bash
|
|
105
|
+
ekodide pair # GERA uma frase-código, guarda e mostra pra ditar no outro
|
|
106
|
+
ekodide pair casa-vento-rio-azul-pedra-lobo # RECEBE a frase ditada pelo outro aparelho
|
|
107
|
+
ekodide pair --palavras 8 # frase mais longa (mais forte) ao gerar
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `firewall` — liberar a entrada (no lado que recebe)
|
|
111
|
+
```bash
|
|
112
|
+
ekodide firewall # detecta o firewall, diz o que falta e mostra o comando
|
|
113
|
+
ekodide firewall --abrir # executa pra liberar (você autoriza)
|
|
114
|
+
```
|
|
115
|
+
Sabe lidar com **Linux** (firewalld/ufw — por porta, pede sudo), **Windows** (netsh —
|
|
116
|
+
por porta, precisa de um Prompt de Administrador) e **macOS** (Application Firewall —
|
|
117
|
+
é por **aplicativo**, libera o Python; e costuma vir desligado, aí nem precisa).
|
|
118
|
+
|
|
119
|
+
### `serve` — receber (abrir a caixa)
|
|
120
|
+
```bash
|
|
121
|
+
ekodide serve # escuta e grava o que chegar (padrão: ~/Downloads)
|
|
122
|
+
ekodide serve --host 0.0.0.0 # abre na LAN (pra receber de outro aparelho)
|
|
123
|
+
ekodide serve --dir ~/Recebidos # escolhe a pasta destino
|
|
124
|
+
ekodide serve --compartilhar ~/Publica # deixa o outro lado PUXAR dessa pasta (ver abaixo)
|
|
125
|
+
```
|
|
126
|
+
Deixe rodando num terminal; `Ctrl+C` para parar.
|
|
127
|
+
|
|
128
|
+
### `list` / `pull` — puxar de outro aparelho
|
|
129
|
+
|
|
130
|
+
O contrário do `send`: em vez de empurrar, você **puxa** um arquivo que o outro lado
|
|
131
|
+
deixou disponível. O outro precisa estar servindo **com a pasta compartilhada**
|
|
132
|
+
(`ekodide serve --compartilhar <pasta>`); sem isso, nada é exposto pra leitura.
|
|
133
|
+
```bash
|
|
134
|
+
ekodide list --de pc # vê o que o 'pc' compartilha pra puxar
|
|
135
|
+
ekodide pull relatorio.pdf --de pc # puxa o arquivo pra cá (padrão: ~/Downloads)
|
|
136
|
+
ekodide pull Fotos/foto.jpg --de pc --dir ~/Recebidos # de subpasta, salvando onde quiser
|
|
137
|
+
```
|
|
138
|
+
Puxar é **opt-in**: só funciona contra quem serviu com `--compartilhar`. O conteúdo
|
|
139
|
+
volta cifrado (cofre) e lacrado (HMAC), e a pasta compartilhada é **cercada** — um
|
|
140
|
+
pedido com `../` nunca escapa dela.
|
|
141
|
+
|
|
142
|
+
### `config` — ajustar
|
|
143
|
+
```bash
|
|
144
|
+
ekodide config show # ver segredo (mascarado) e destinos
|
|
145
|
+
ekodide config destino pc http://IP:8778 # cadastrar/atualizar um destino
|
|
146
|
+
ekodide config segredo "a-chave" # trocar o segredo
|
|
147
|
+
```
|
|
148
|
+
A config fica em `~/.config/ekodide/config.json` (cadeado `600`, porque tem o segredo).
|
|
149
|
+
|
|
150
|
+
## Receitas
|
|
151
|
+
|
|
152
|
+
**📤 PC → celular** (com o celular já escutando):
|
|
153
|
+
```bash
|
|
154
|
+
# no PC:
|
|
155
|
+
ekodide send relatorio.pdf --para celular
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**📥 celular → PC** (abra a caixa do PC primeiro):
|
|
159
|
+
```bash
|
|
160
|
+
# no PC (deixe rodando):
|
|
161
|
+
ekodide serve --host 0.0.0.0
|
|
162
|
+
# no outro aparelho:
|
|
163
|
+
ekodide send foto.jpg --para pc
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 3 pegadinhas
|
|
167
|
+
|
|
168
|
+
1. **A caixa precisa estar aberta:** o lado que recebe tem que estar com
|
|
169
|
+
`ekodide serve` no ar.
|
|
170
|
+
2. **Mesmo segredo nos dois lados** — é a chave do cadeado.
|
|
171
|
+
3. **Firewall:** quem recebe precisa liberar **TCP 8778** (transferência) e **UDP 8779**
|
|
172
|
+
(descoberta). O jeito fácil: **`ekodide firewall --abrir`** (detecta firewalld/ufw e
|
|
173
|
+
roda com sudo). Na mão (Fedora): `sudo firewall-cmd --add-port=8778/tcp
|
|
174
|
+
--add-port=8779/udp --permanent && sudo systemctl restart firewalld`. Sintoma de
|
|
175
|
+
porta fechada: `No route to host` no envio (ou ninguém aparece no `devices`).
|
|
176
|
+
|
|
177
|
+
## Usar como biblioteca
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from pathlib import Path
|
|
181
|
+
from ekodide import enviar, servir
|
|
182
|
+
|
|
183
|
+
r = enviar(Path("foto.png"), "http://192.168.0.10:8778", segredo="...")
|
|
184
|
+
print(r.ok, r.destino)
|
|
185
|
+
|
|
186
|
+
# na outra ponta:
|
|
187
|
+
servir(Path("~/Downloads").expanduser(), segredo="...", host="0.0.0.0")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Android
|
|
191
|
+
|
|
192
|
+
Em breve via **app nativo** (o celular como ponta passiva que o PC comanda).
|
|
193
|
+
Em desenvolvimento.
|
|
194
|
+
|
|
195
|
+
## Como é por dentro
|
|
196
|
+
|
|
197
|
+
| cômodo | papel |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `lacre.py` | fechadura HMAC — o segredo nunca trafega |
|
|
200
|
+
| `cofre.py` | cifra o conteúdo (AES-256-GCM) — embaralhado na rede, idêntico no destino |
|
|
201
|
+
| `carteiro.py` | envia arquivo/pasta; arquivo grande vai **picado** em pedaços |
|
|
202
|
+
| `caixa_postal.py` | grava cercado (sem travessia, sem sobrescrever) e remonta os pedaços |
|
|
203
|
+
| `acervo.py` | LÊ cercado a pasta compartilhada pro "puxar" (sem travessia, sem fuga por symlink) |
|
|
204
|
+
| `buscador.py` | PUXA arquivo de outra ponta (pede, decifra, grava reusando a caixa postal) |
|
|
205
|
+
| `recebedor.py` | servidor HTTP leve que escuta e grava (e expõe /listar e /buscar pro puxar) |
|
|
206
|
+
| `vizinhanca.py` | descoberta na LAN: anuncia presença e acha aparelhos pelo nome (sem IP) |
|
|
207
|
+
| `frase.py` | gera o segredo como frase-código digitável (pareamento out-of-band) |
|
|
208
|
+
| `cortina.py` | detecta o firewall e monta/roda o comando pra liberar as portas |
|
|
209
|
+
| `config.py` | lê/grava `~/.config/ekodide/config.json` (segredo + destinos + nome) |
|
|
210
|
+
| `cli.py` | o comando `ekodide` (send/serve/list/pull/devices/pair/firewall/config) |
|
|
211
|
+
|
|
212
|
+
## Segurança (honesto)
|
|
213
|
+
|
|
214
|
+
Duas camadas, ambas chaveadas pelo segredo que as pontas compartilham (a frase-código
|
|
215
|
+
do pareamento — nunca trafega):
|
|
216
|
+
|
|
217
|
+
- **Lacre (HMAC-SHA256):** prova *quem* mandou, que ninguém mexeu no caminho, e que a
|
|
218
|
+
mensagem é recente (janela de 5 min).
|
|
219
|
+
- **Cofre (AES-256-GCM):** **cifra o conteúdo** — na rede passa só embaralhado; só
|
|
220
|
+
remetente e destinatário leem. O arquivo gravado fica byte-idêntico ao original.
|
|
221
|
+
|
|
222
|
+
Ainda é **mesma rede (Wi-Fi)** por foco, não por limite de cifra. O que falta pra
|
|
223
|
+
"rua" (internet) é endereçamento/NAT, não proteção do conteúdo.
|
|
224
|
+
|
|
225
|
+
## Licença
|
|
226
|
+
|
|
227
|
+
MIT — veja [LICENSE](LICENSE).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Ekodide — envia e recebe arquivos pela rede, lacrados e idênticos.
|
|
2
|
+
|
|
3
|
+
O nome é de um papagaio africano (odídẹ): repete com perfeição (o arquivo chega
|
|
4
|
+
cópia EXATA, sha256 idêntico) e VOA (vai de um aparelho a outro pela rede, sem
|
|
5
|
+
cabo). É código burro e determinístico — não tem IA aqui dentro. Algo pode ACIONAR
|
|
6
|
+
o Ekodide (um humano no terminal, um script, um agente), mas quem faz o trabalho é
|
|
7
|
+
este maquinário fixo.
|
|
8
|
+
|
|
9
|
+
Como biblioteca:
|
|
10
|
+
from ekodide import enviar, servir
|
|
11
|
+
enviar(origem, url, segredo) # manda arquivo/pasta
|
|
12
|
+
servir(base, segredo, host="0.0.0.0") # escuta e grava
|
|
13
|
+
|
|
14
|
+
Como comando (depois de instalar):
|
|
15
|
+
ekodide send arquivo --para pc
|
|
16
|
+
ekodide serve
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from .buscador import ErroPuxar, puxar
|
|
21
|
+
from .buscador import listar as listar_remoto
|
|
22
|
+
from .caixa_postal import gravar_recebido, guardar, guardar_pedaco
|
|
23
|
+
from .carteiro import EnvioResultado, enviar
|
|
24
|
+
from .lacre import (
|
|
25
|
+
JANELA_SEGUNDOS,
|
|
26
|
+
TrancaInvalida,
|
|
27
|
+
desempacotar,
|
|
28
|
+
empacotar,
|
|
29
|
+
segredo_do_ambiente,
|
|
30
|
+
)
|
|
31
|
+
from .recebedor import servir
|
|
32
|
+
from .vizinhanca import anunciar, anunciar_em_thread, procurar
|
|
33
|
+
from .frase import gerar as gerar_frase
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"enviar",
|
|
39
|
+
"EnvioResultado",
|
|
40
|
+
"puxar",
|
|
41
|
+
"listar_remoto",
|
|
42
|
+
"ErroPuxar",
|
|
43
|
+
"servir",
|
|
44
|
+
"gravar_recebido",
|
|
45
|
+
"guardar",
|
|
46
|
+
"guardar_pedaco",
|
|
47
|
+
"empacotar",
|
|
48
|
+
"desempacotar",
|
|
49
|
+
"segredo_do_ambiente",
|
|
50
|
+
"TrancaInvalida",
|
|
51
|
+
"JANELA_SEGUNDOS",
|
|
52
|
+
"anunciar",
|
|
53
|
+
"anunciar_em_thread",
|
|
54
|
+
"procurar",
|
|
55
|
+
"gerar_frase",
|
|
56
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""O acervo do Ekodide: o lado de LEITURA que o 'puxar' expõe.
|
|
2
|
+
|
|
3
|
+
Espelho do caixa_postal (que GRAVA o que chega): aqui a gente LÊ arquivos de dentro
|
|
4
|
+
de uma pasta COMPARTILHADA — com a mesma cerca, e um cuidado a mais. Um ponto de
|
|
5
|
+
leitura é poder; então só se lê de dentro do que foi explicitamente compartilhado, e:
|
|
6
|
+
|
|
7
|
+
- travessia de caminho ('../') é descartada — nunca escapa da pasta;
|
|
8
|
+
- symlink que aponta pra FORA da pasta é recusado (o alvo real cai fora da cerca) —
|
|
9
|
+
risco que o lado de escrita não corre, mas o de leitura sim;
|
|
10
|
+
- os temporários de recebimento ('.parcial'/'.parcial.meta') NÃO entram na lista
|
|
11
|
+
(são montagem em andamento, não arquivo pra compartilhar).
|
|
12
|
+
|
|
13
|
+
Lógica pura (sem rede, sem variáveis de ambiente): recebe a pasta `base` e um nome
|
|
14
|
+
relativo. Quem expõe isso pela rede (HTTP) é o recebedor (rotas /listar e /buscar).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
# Mesmo tamanho de pedaço do carteiro: arquivo grande é lido/devolvido picado, e cada
|
|
21
|
+
# pedaço cabe no corpo de 32 MB do recebedor mesmo depois do base64 inchar ~33%.
|
|
22
|
+
PEDACO = 16 * 1024 * 1024
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolver_dentro(nome: str, base: Path) -> Path:
|
|
26
|
+
"""De um `nome` relativo ('Fotos/sub/img.png') devolve o caminho REAL dentro de
|
|
27
|
+
`base`, NUNCA escapando. Componentes perigosos ('', '.', '..', raiz absoluta) são
|
|
28
|
+
descartados; o resultado é resolvido (segue symlink de propósito) e conferido contra
|
|
29
|
+
a base — assim um atalho apontando pra fora é pego. Levanta ValueError se escapar."""
|
|
30
|
+
base = base.resolve()
|
|
31
|
+
rel = Path(nome)
|
|
32
|
+
if rel.is_absolute(): # caminho absoluto vira relativo (tira a raiz '/')
|
|
33
|
+
rel = Path(*rel.parts[1:])
|
|
34
|
+
partes = [p for p in rel.parts if p not in ("", ".", "..")]
|
|
35
|
+
if not partes:
|
|
36
|
+
raise ValueError("nome de arquivo inválido")
|
|
37
|
+
alvo = base.joinpath(*partes).resolve() # resolve() segue symlink: pega fuga por atalho
|
|
38
|
+
if base != alvo and base not in alvo.parents:
|
|
39
|
+
raise ValueError("fora da pasta compartilhada")
|
|
40
|
+
return alvo
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _e_compartilhavel(p: Path) -> bool:
|
|
44
|
+
"""Arquivo comum de verdade — fora os temporários de recebimento em montagem."""
|
|
45
|
+
return p.is_file() and not p.name.endswith((".parcial", ".parcial.meta"))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def listar(base: Path) -> list[dict]:
|
|
49
|
+
"""O que dá pra puxar: lista de {'nome': caminho-relativo, 'tamanho': bytes},
|
|
50
|
+
ordenada por nome, recursiva (preserva subpastas). Pasta inexistente ou não
|
|
51
|
+
compartilhada (None) -> lista vazia: nada fica exposto sem querer."""
|
|
52
|
+
if base is None:
|
|
53
|
+
return []
|
|
54
|
+
base = Path(base).expanduser()
|
|
55
|
+
if not base.is_dir():
|
|
56
|
+
return []
|
|
57
|
+
base = base.resolve()
|
|
58
|
+
achados = []
|
|
59
|
+
for p in sorted(base.rglob("*")):
|
|
60
|
+
if _e_compartilhavel(p):
|
|
61
|
+
achados.append({"nome": p.relative_to(base).as_posix(), "tamanho": p.stat().st_size})
|
|
62
|
+
return achados
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def tamanho_de(nome: str, base: Path) -> int:
|
|
66
|
+
"""Tamanho (bytes) de um arquivo compartilhado. Mesma cerca do `ler_pedaco`."""
|
|
67
|
+
alvo = _resolver_dentro(nome, Path(base).expanduser())
|
|
68
|
+
if not _e_compartilhavel(alvo):
|
|
69
|
+
raise ValueError("arquivo não disponível")
|
|
70
|
+
return alvo.stat().st_size
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ler_pedaco(nome: str, base: Path, parte: int, partes: int) -> bytes:
|
|
74
|
+
"""Lê o pedaço `parte` (de `partes`) do arquivo, cada um de até PEDACO bytes (o
|
|
75
|
+
último vem menor). Arquivo que cabe num pedaço vai inteiro com parte=0, partes=1.
|
|
76
|
+
Cerca de segurança: nunca lê fora da pasta compartilhada."""
|
|
77
|
+
if partes < 1 or parte < 0 or parte >= partes:
|
|
78
|
+
raise ValueError("índice de pedaço inválido")
|
|
79
|
+
alvo = _resolver_dentro(nome, Path(base).expanduser())
|
|
80
|
+
if not _e_compartilhavel(alvo):
|
|
81
|
+
raise ValueError("arquivo não disponível")
|
|
82
|
+
with alvo.open("rb") as f:
|
|
83
|
+
f.seek(parte * PEDACO)
|
|
84
|
+
return f.read(PEDACO)
|