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 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).
@@ -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)