neev 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.
- neev-0.1.0/PKG-INFO +595 -0
- neev-0.1.0/README.md +572 -0
- neev-0.1.0/pyproject.toml +166 -0
- neev-0.1.0/src/neev/__init__.py +6 -0
- neev-0.1.0/src/neev/auth.py +274 -0
- neev-0.1.0/src/neev/cli.py +309 -0
- neev-0.1.0/src/neev/config.py +52 -0
- neev-0.1.0/src/neev/fs.py +177 -0
- neev-0.1.0/src/neev/html.py +235 -0
- neev-0.1.0/src/neev/html_entries.py +298 -0
- neev-0.1.0/src/neev/html_icons.py +239 -0
- neev-0.1.0/src/neev/html_login.py +212 -0
- neev-0.1.0/src/neev/html_markdown.py +152 -0
- neev-0.1.0/src/neev/html_markdown_assets.py +226 -0
- neev-0.1.0/src/neev/html_nav.py +115 -0
- neev-0.1.0/src/neev/html_page_template.py +265 -0
- neev-0.1.0/src/neev/html_preview.py +209 -0
- neev-0.1.0/src/neev/html_upload.py +292 -0
- neev-0.1.0/src/neev/html_upload_js.py +137 -0
- neev-0.1.0/src/neev/log.py +37 -0
- neev-0.1.0/src/neev/py.typed +0 -0
- neev-0.1.0/src/neev/server.py +270 -0
- neev-0.1.0/src/neev/server_assets.py +74 -0
- neev-0.1.0/src/neev/server_auth.py +120 -0
- neev-0.1.0/src/neev/server_core.py +224 -0
- neev-0.1.0/src/neev/server_preview.py +89 -0
- neev-0.1.0/src/neev/server_upload.py +111 -0
- neev-0.1.0/src/neev/server_utils.py +19 -0
- neev-0.1.0/src/neev/server_zip.py +95 -0
- neev-0.1.0/src/neev/static/alpine.min.js +5 -0
- neev-0.1.0/src/neev/static/neev.css +2 -0
- neev-0.1.0/src/neev/toml_config.py +76 -0
- neev-0.1.0/src/neev/upload.py +223 -0
- neev-0.1.0/src/neev/upload_multipart.py +175 -0
- neev-0.1.0/src/neev/url_utils.py +84 -0
- neev-0.1.0/src/neev/zip.py +246 -0
neev-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: neev
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-dependency Python CLI for sharing local directories over HTTP with authentication, file browsing, and ZIP downloads
|
|
5
|
+
Keywords: http,file-server,cli,sharing
|
|
6
|
+
Author: Akshay Prabhu
|
|
7
|
+
Author-email: Akshay Prabhu <akshay.prabhu.mulki@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Project-URL: Homepage, https://github.com/prabhuakshay/neev
|
|
19
|
+
Project-URL: Repository, https://github.com/prabhuakshay/neev
|
|
20
|
+
Project-URL: Issues, https://github.com/prabhuakshay/neev/issues
|
|
21
|
+
Project-URL: Changelog, https://github.com/prabhuakshay/neev/releases
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img src="assets/neev-icon.jpg" alt="neev" width="120">
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<h1 align="center">neev</h1>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<em>Zero-dependency Python CLI for sharing local directories over HTTP — with auth, file browsing, ZIP downloads, and uploads.</em>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://pypi.org/project/neev/"><img src="https://img.shields.io/pypi/v/neev.svg?color=blue" alt="PyPI version"></a>
|
|
36
|
+
<a href="https://pypi.org/project/neev/"><img src="https://img.shields.io/pypi/pyversions/neev.svg" alt="Python versions"></a>
|
|
37
|
+
<a href="https://pypi.org/project/neev/"><img src="https://img.shields.io/pypi/dm/neev.svg" alt="PyPI downloads"></a>
|
|
38
|
+
<a href="https://github.com/prabhuakshay/neev/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/neev.svg" alt="License"></a>
|
|
39
|
+
<a href="https://github.com/prabhuakshay/neev/actions/workflows/ci.yml"><img src="https://github.com/prabhuakshay/neev/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
40
|
+
<a href="https://github.com/prabhuakshay/neev/actions/workflows/publish.yml"><img src="https://github.com/prabhuakshay/neev/actions/workflows/publish.yml/badge.svg" alt="Publish"></a>
|
|
41
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
|
|
42
|
+
<img src="https://img.shields.io/badge/coverage-%E2%89%A595%25-brightgreen" alt="Coverage">
|
|
43
|
+
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
|
|
44
|
+
<a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv"></a>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Table of Contents
|
|
50
|
+
|
|
51
|
+
- [Why neev?](#why-neev)
|
|
52
|
+
- [Install](#install)
|
|
53
|
+
- [Quick Start](#quick-start)
|
|
54
|
+
- [CLI Reference](#cli-reference)
|
|
55
|
+
- [Configuration File (`neev.toml`)](#configuration-file-neevtoml)
|
|
56
|
+
- [Environment Variables](#environment-variables)
|
|
57
|
+
- [Configuration Precedence](#configuration-precedence)
|
|
58
|
+
- [Features](#features)
|
|
59
|
+
- [HTTP API](#http-api)
|
|
60
|
+
- [Security Model](#security-model)
|
|
61
|
+
- [Recipes](#recipes)
|
|
62
|
+
- [Architecture](#architecture)
|
|
63
|
+
- [Development](#development)
|
|
64
|
+
- [Troubleshooting](#troubleshooting)
|
|
65
|
+
- [FAQ](#faq)
|
|
66
|
+
- [Contributing](#contributing)
|
|
67
|
+
- [License](#license)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Why neev?
|
|
72
|
+
|
|
73
|
+
`python -m http.server` is great, but it has no authentication, no way to download a folder, no uploads, and exposes dotfiles by default. neev is the drop-in replacement with the things you actually need:
|
|
74
|
+
|
|
75
|
+
- **HTTP Basic Auth** — constant-time credential comparison
|
|
76
|
+
- **ZIP folder downloads** — streamed on the fly, no temp files
|
|
77
|
+
- **File uploads** — opt-in, with size limits and path-traversal protection
|
|
78
|
+
- **Clean browser UI** — dark/light theme, Markdown preview, syntax-aware file icons
|
|
79
|
+
- **HTTP Range support** — resumable downloads, video/audio seeking
|
|
80
|
+
- **Concurrent requests** — threaded server, not single-request-at-a-time
|
|
81
|
+
- **Secure defaults** — localhost-only, no writes, no hidden files
|
|
82
|
+
- **Zero dependencies** — pure Python stdlib, Python 3.11+
|
|
83
|
+
|
|
84
|
+
Built for developers sharing build artifacts, teams exchanging files on a LAN, and anyone who wants `http.server` but grown up.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
### From PyPI (recommended)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# with uv (fastest)
|
|
94
|
+
uv tool install neev
|
|
95
|
+
|
|
96
|
+
# or pipx
|
|
97
|
+
pipx install neev
|
|
98
|
+
|
|
99
|
+
# or plain pip
|
|
100
|
+
pip install neev
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Run without installing
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uvx neev --dir ./public
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### From source
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/prabhuakshay/neev
|
|
113
|
+
cd neev
|
|
114
|
+
uv sync
|
|
115
|
+
uv run neev --help
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Requires Python 3.11 or newer.** Works on Linux, macOS, and Windows.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Quick Start
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Serve the current directory on http://127.0.0.1:8000
|
|
126
|
+
neev
|
|
127
|
+
|
|
128
|
+
# Serve a specific directory
|
|
129
|
+
neev ./public
|
|
130
|
+
|
|
131
|
+
# With auth, on all interfaces, custom port
|
|
132
|
+
neev ./share --host 0.0.0.0 --port 8080 --auth alice:s3cret
|
|
133
|
+
|
|
134
|
+
# Full-featured: auth + uploads + zip downloads
|
|
135
|
+
neev ./share --auth alice:s3cret --enable-upload --enable-zip-download
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Open the URL printed on startup. You'll see a file browser. If auth is enabled, your browser will prompt for credentials.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## CLI Reference
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
neev [DIRECTORY] [OPTIONS]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Positional arguments
|
|
149
|
+
|
|
150
|
+
| Argument | Default | Description |
|
|
151
|
+
|----------|---------|-------------|
|
|
152
|
+
| `directory` | `.` | Directory to serve. Must exist. Resolved to its real path (symlinks followed). |
|
|
153
|
+
|
|
154
|
+
### Options
|
|
155
|
+
|
|
156
|
+
| Flag | Default | Description |
|
|
157
|
+
|------|---------|-------------|
|
|
158
|
+
| `--host HOST` | `127.0.0.1` | Address to bind. Use `0.0.0.0` to expose on LAN. |
|
|
159
|
+
| `--port PORT`, `-p PORT` | `8000` | TCP port (1–65535). |
|
|
160
|
+
| `--auth USER:PASS` | _(none)_ | Enable HTTP Basic Auth. Equivalent to `NEEV_AUTH` env var. |
|
|
161
|
+
| `--show-hidden` / `--no-show-hidden` | off | Show dotfiles and `neev.toml` in listings. |
|
|
162
|
+
| `--enable-zip-download` / `--no-enable-zip-download` | off | Allow folders to be downloaded as streamed ZIP. |
|
|
163
|
+
| `--max-zip-size MB` | `100` | Maximum ZIP archive size in MB. Rejected if exceeded. |
|
|
164
|
+
| `--enable-upload` / `--no-enable-upload` | off | Allow multipart file uploads from the browser. |
|
|
165
|
+
| `--read-only` / `--no-read-only` | off | Force-disable uploads (overrides `--enable-upload`). |
|
|
166
|
+
| `--banner TEXT` | _(none)_ | Message displayed at the top of directory listings. |
|
|
167
|
+
| `-h`, `--help` | — | Show help and exit. |
|
|
168
|
+
|
|
169
|
+
All boolean flags use `argparse.BooleanOptionalAction`, so `--no-<flag>` works too — useful for overriding `neev.toml` from the CLI.
|
|
170
|
+
|
|
171
|
+
### Examples
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Read-only share even if TOML enables uploads
|
|
175
|
+
neev ./build --read-only
|
|
176
|
+
|
|
177
|
+
# Serve on LAN with auth, show dotfiles, custom banner
|
|
178
|
+
neev ./code --host 0.0.0.0 --auth me:pw --show-hidden --banner "Internal only"
|
|
179
|
+
|
|
180
|
+
# Raise ZIP size cap to 500 MB
|
|
181
|
+
neev ./data --enable-zip-download --max-zip-size 500
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Configuration File (`neev.toml`)
|
|
187
|
+
|
|
188
|
+
neev looks for a `neev.toml` file **in the served directory** and merges it with CLI args. CLI flags always win.
|
|
189
|
+
|
|
190
|
+
### Example
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
# ./neev.toml
|
|
194
|
+
host = "0.0.0.0"
|
|
195
|
+
port = 9000
|
|
196
|
+
show-hidden = false
|
|
197
|
+
enable-zip-download = true
|
|
198
|
+
max-zip-size = 250
|
|
199
|
+
enable-upload = false
|
|
200
|
+
read-only = false
|
|
201
|
+
banner = "Build artifacts — ask #devops for access"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Recognized keys
|
|
205
|
+
|
|
206
|
+
| TOML key | Type | Notes |
|
|
207
|
+
|----------|------|-------|
|
|
208
|
+
| `host` | string | Same as `--host`. |
|
|
209
|
+
| `port` | integer | Same as `--port`. |
|
|
210
|
+
| `auth` | string | `"user:pass"`. Same as `--auth`. |
|
|
211
|
+
| `show-hidden` | bool | Same as `--show-hidden`. |
|
|
212
|
+
| `enable-zip-download` | bool | Same as `--enable-zip-download`. |
|
|
213
|
+
| `max-zip-size` | integer | MB. Same as `--max-zip-size`. |
|
|
214
|
+
| `enable-upload` | bool | Same as `--enable-upload`. |
|
|
215
|
+
| `read-only` | bool | Same as `--read-only`. |
|
|
216
|
+
| `banner` | string | Same as `--banner`. |
|
|
217
|
+
|
|
218
|
+
**Denied keys:** `directory` is never read from TOML (the served directory is always set by CLI, to avoid surprise path changes).
|
|
219
|
+
|
|
220
|
+
**Unknown keys** are logged at WARN level and ignored. **Malformed TOML** is logged and the file is skipped — neev keeps running with CLI defaults.
|
|
221
|
+
|
|
222
|
+
The `neev.toml` file itself is hidden from listings unless `--show-hidden` is set.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Environment Variables
|
|
227
|
+
|
|
228
|
+
| Variable | Purpose |
|
|
229
|
+
|----------|---------|
|
|
230
|
+
| `NEEV_AUTH` | Credentials as `user:pass`. Alternative to `--auth`. |
|
|
231
|
+
|
|
232
|
+
`--auth` beats `NEEV_AUTH` when both are set.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Configuration Precedence
|
|
237
|
+
|
|
238
|
+
From highest to lowest:
|
|
239
|
+
|
|
240
|
+
1. **CLI flags** (explicit `--foo bar`)
|
|
241
|
+
2. **`NEEV_AUTH`** env var (auth only)
|
|
242
|
+
3. **`neev.toml`** in the served directory
|
|
243
|
+
4. **Built-in defaults** (see CLI reference table)
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Features
|
|
248
|
+
|
|
249
|
+
### File browser
|
|
250
|
+
|
|
251
|
+
- Directory listing with sortable columns (name, size, modified).
|
|
252
|
+
- Per-file icons by extension/type.
|
|
253
|
+
- Breadcrumb navigation.
|
|
254
|
+
- In-browser preview for text files, Markdown (rendered), images, and PDFs.
|
|
255
|
+
- Correct MIME types and charset detection per file.
|
|
256
|
+
|
|
257
|
+
### ZIP folder downloads
|
|
258
|
+
|
|
259
|
+
- Triggered by a "Download as ZIP" button on any folder (if `--enable-zip-download`).
|
|
260
|
+
- **Streamed** — no temp files, constant memory usage.
|
|
261
|
+
- Capped at `--max-zip-size` MB; oversized archives return `413 Payload Too Large`.
|
|
262
|
+
- Hidden files excluded unless `--show-hidden`.
|
|
263
|
+
|
|
264
|
+
### Uploads
|
|
265
|
+
|
|
266
|
+
- Opt-in via `--enable-upload`. Disabled under `--read-only`.
|
|
267
|
+
- Multipart upload form on each directory page.
|
|
268
|
+
- Filename sanitization — path traversal, null bytes, and absolute paths rejected.
|
|
269
|
+
- Writes land in the directory currently being viewed.
|
|
270
|
+
- Authentication (if configured) is required.
|
|
271
|
+
|
|
272
|
+
### HTTP Range requests
|
|
273
|
+
|
|
274
|
+
Partial content (`206`) supported for all file downloads — resumable downloads, video/audio seeking, `curl -C -`.
|
|
275
|
+
|
|
276
|
+
### Concurrency
|
|
277
|
+
|
|
278
|
+
Threaded `HTTPServer` — multiple clients can browse, download, and upload simultaneously.
|
|
279
|
+
|
|
280
|
+
### Authentication
|
|
281
|
+
|
|
282
|
+
- HTTP Basic Auth.
|
|
283
|
+
- Credentials compared with `hmac.compare_digest` (constant-time).
|
|
284
|
+
- Failed auth returns `401` with a `WWW-Authenticate: Basic` challenge.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## HTTP API
|
|
289
|
+
|
|
290
|
+
neev is primarily browser-driven, but every interaction is a plain HTTP request — scriptable with `curl`, `wget`, `httpie`, etc.
|
|
291
|
+
|
|
292
|
+
### Listing / serving files
|
|
293
|
+
|
|
294
|
+
```http
|
|
295
|
+
GET /path/to/dir/ → HTML directory listing
|
|
296
|
+
GET /path/to/file.txt → file contents (with Range support)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Trailing slash → directory; no slash → file. Requests to a path without a trailing slash that resolves to a directory are redirected with `301` to the canonical slashed URL.
|
|
300
|
+
|
|
301
|
+
### ZIP download
|
|
302
|
+
|
|
303
|
+
```http
|
|
304
|
+
GET /path/to/dir/?zip=1
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Returns `application/zip` stream. Filename is `<dirname>.zip`. Disabled unless `--enable-zip-download`.
|
|
308
|
+
|
|
309
|
+
### File preview (rendered)
|
|
310
|
+
|
|
311
|
+
```http
|
|
312
|
+
GET /path/to/file.md?preview=1
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Returns HTML with Markdown rendered server-side. Works for text and Markdown files.
|
|
316
|
+
|
|
317
|
+
### Upload
|
|
318
|
+
|
|
319
|
+
```http
|
|
320
|
+
POST /path/to/dir/
|
|
321
|
+
Content-Type: multipart/form-data; boundary=...
|
|
322
|
+
|
|
323
|
+
<file field named "file">
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Returns `303 See Other` redirect to the directory on success, `4xx` on validation failure. Disabled unless `--enable-upload` and not `--read-only`.
|
|
327
|
+
|
|
328
|
+
Example with curl:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
curl -u "$USER_PASS" -F "file=@./report.pdf" http://localhost:8000/uploads/
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Authentication
|
|
335
|
+
|
|
336
|
+
Every endpoint honors Basic Auth when enabled:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
curl -u "$USER_PASS" http://localhost:8000/
|
|
340
|
+
# or (equivalent) — build the header yourself
|
|
341
|
+
curl -H "Authorization: Basic $(printf '%s' "$USER_PASS" | base64)" http://localhost:8000/
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Status codes
|
|
345
|
+
|
|
346
|
+
| Code | When |
|
|
347
|
+
|------|------|
|
|
348
|
+
| `200 OK` | Successful GET of file/listing. |
|
|
349
|
+
| `206 Partial Content` | Successful Range request. |
|
|
350
|
+
| `301 Moved Permanently` | Directory URL missing trailing slash. |
|
|
351
|
+
| `303 See Other` | Successful upload. |
|
|
352
|
+
| `400 Bad Request` | Malformed request / upload. |
|
|
353
|
+
| `401 Unauthorized` | Auth required or invalid credentials. |
|
|
354
|
+
| `403 Forbidden` | Path traversal, access denied, or writes on read-only. |
|
|
355
|
+
| `404 Not Found` | Path doesn't exist or hidden without `--show-hidden`. |
|
|
356
|
+
| `405 Method Not Allowed` | e.g. `POST` when uploads disabled. |
|
|
357
|
+
| `413 Payload Too Large` | ZIP exceeds `--max-zip-size`. |
|
|
358
|
+
| `416 Range Not Satisfiable` | Invalid Range header. |
|
|
359
|
+
| `500 Internal Server Error` | Unexpected server fault. |
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Security Model
|
|
364
|
+
|
|
365
|
+
**What neev protects against:**
|
|
366
|
+
|
|
367
|
+
- **Path traversal** — every request resolves `os.path.realpath()` and verifies the result is a descendant of the served directory. `../`, symlink escapes, and absolute paths are rejected with `403`.
|
|
368
|
+
- **Credential leaks in timing** — `hmac.compare_digest` for both username and password.
|
|
369
|
+
- **Upload filename injection** — rejects traversal, null bytes, and absolute paths.
|
|
370
|
+
- **Accidental exposure** — defaults are localhost-only, no writes, no dotfiles, no ZIPs.
|
|
371
|
+
- **Oversized ZIP abuse** — streaming ZIPs are bounded by `--max-zip-size`.
|
|
372
|
+
|
|
373
|
+
**What neev does NOT do:**
|
|
374
|
+
|
|
375
|
+
- No HTTPS/TLS. Run behind a reverse proxy (nginx, Caddy) for internet-facing deployments.
|
|
376
|
+
- No rate limiting. Use a reverse proxy or firewall.
|
|
377
|
+
- No user management — single Basic Auth pair, one shared credential.
|
|
378
|
+
- No virus scanning on uploads.
|
|
379
|
+
- No audit log beyond stdout request logging.
|
|
380
|
+
|
|
381
|
+
**If exposing to a network or internet:**
|
|
382
|
+
|
|
383
|
+
1. Always set `--auth`.
|
|
384
|
+
2. Use a strong password (treat as a bearer token).
|
|
385
|
+
3. Put it behind a TLS-terminating proxy.
|
|
386
|
+
4. Prefer `--read-only` unless uploads are required.
|
|
387
|
+
5. Keep `--show-hidden` off.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Recipes
|
|
392
|
+
|
|
393
|
+
### Share a build artifact with a coworker
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
neev ./dist --host 0.0.0.0 --auth reviewer:pleasecheck
|
|
397
|
+
# share: http://<your-ip>:8000/
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Drop box — let someone upload files to you
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
mkdir -p ~/inbox
|
|
404
|
+
neev ~/inbox --host 0.0.0.0 --auth sender:pw --enable-upload
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Static preview site with Markdown rendering
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
neev ./docs --enable-zip-download --banner "Project docs"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Behind Caddy with TLS
|
|
414
|
+
|
|
415
|
+
```caddy
|
|
416
|
+
share.example.com {
|
|
417
|
+
reverse_proxy localhost:8000
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
neev /srv/share --auth alice:s3cret --enable-zip-download
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Systemd service
|
|
426
|
+
|
|
427
|
+
```ini
|
|
428
|
+
# /etc/systemd/system/neev.service
|
|
429
|
+
[Unit]
|
|
430
|
+
Description=neev file server
|
|
431
|
+
After=network.target
|
|
432
|
+
|
|
433
|
+
[Service]
|
|
434
|
+
ExecStart=/usr/local/bin/neev /srv/share --host 127.0.0.1 --port 8000 --enable-zip-download
|
|
435
|
+
Environment=NEEV_AUTH=alice:s3cret
|
|
436
|
+
Restart=on-failure
|
|
437
|
+
User=neev
|
|
438
|
+
|
|
439
|
+
[Install]
|
|
440
|
+
WantedBy=multi-user.target
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Docker one-liner
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
docker run --rm -p 8000:8000 -v "$PWD:/srv:ro" python:3.13-slim \
|
|
447
|
+
sh -c "pip install neev && neev /srv --host 0.0.0.0 --read-only"
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Architecture
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
src/neev/
|
|
456
|
+
├── cli.py # argparse, validation, entry point
|
|
457
|
+
├── config.py # frozen Config dataclass
|
|
458
|
+
├── toml_config.py # neev.toml loader + CLI merge
|
|
459
|
+
├── server.py # HTTPServer wiring (threaded)
|
|
460
|
+
├── server_core.py # main request dispatcher
|
|
461
|
+
├── server_auth.py # HTTP Basic Auth
|
|
462
|
+
├── server_preview.py # file/Markdown preview
|
|
463
|
+
├── server_upload.py # multipart upload handler
|
|
464
|
+
├── server_zip.py # streaming ZIP response
|
|
465
|
+
├── server_assets.py # bundled static assets (CSS, JS)
|
|
466
|
+
├── server_utils.py # Range, redirects, MIME helpers
|
|
467
|
+
├── fs.py # path-safe filesystem ops
|
|
468
|
+
├── auth.py # constant-time credential check
|
|
469
|
+
├── upload.py # upload validation
|
|
470
|
+
├── upload_multipart.py # multipart body parsing
|
|
471
|
+
├── zip.py # streaming zip generator
|
|
472
|
+
├── url_utils.py # URL encoding/decoding
|
|
473
|
+
├── log.py # logging helpers + ANSI styling
|
|
474
|
+
├── html*.py # HTML templating (no jinja — pure stdlib)
|
|
475
|
+
└── static/ # compiled CSS bundled with package
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Design pillars:**
|
|
479
|
+
|
|
480
|
+
1. **Stdlib only.** Every feature is built on `http.server`, `zipfile`, `argparse`, `html`, `tomllib`, `base64`, `hmac`.
|
|
481
|
+
2. **Defense-in-depth on paths.** Every filesystem op goes through `fs.py` which resolves and verifies containment.
|
|
482
|
+
3. **Frozen config.** `Config` is built once at startup; request handlers never mutate configuration state.
|
|
483
|
+
4. **No dynamic imports, no plugin system.** Small, auditable surface area.
|
|
484
|
+
5. **Files under 300 lines.** Enforced by convention — keeps modules focused.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Development
|
|
489
|
+
|
|
490
|
+
Uses [uv](https://docs.astral.sh/uv/) for everything.
|
|
491
|
+
|
|
492
|
+
### Setup
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
git clone https://github.com/prabhuakshay/neev
|
|
496
|
+
cd neev
|
|
497
|
+
uv sync
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Run
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
uv run neev --help
|
|
504
|
+
uv run neev ./tests/fixtures --enable-zip-download
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Test
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
uv run pytest # full suite with coverage (≥95% enforced)
|
|
511
|
+
uv run pytest -k upload # filter
|
|
512
|
+
uv run pytest --no-cov -x # fast iteration
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Lint & type check
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
uv run ruff check .
|
|
519
|
+
uv run ruff format .
|
|
520
|
+
uv run ty check
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Pre-commit hooks (prek)
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
uv run prek install
|
|
527
|
+
uv run prek run --all-files
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
The pre-commit config also recompiles the Tailwind CSS bundle when any `html_*.py` changes.
|
|
531
|
+
|
|
532
|
+
### Build & publish
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
uv build # produces sdist + wheel in dist/
|
|
536
|
+
# Publishing is automated: create a GitHub Release → PyPI workflow uploads.
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Troubleshooting
|
|
542
|
+
|
|
543
|
+
| Symptom | Likely cause | Fix |
|
|
544
|
+
|---------|-------------|-----|
|
|
545
|
+
| `Address already in use` | Port taken | Pick a different `--port` or kill the prior process. |
|
|
546
|
+
| Browser keeps re-prompting for password | Wrong credentials or auth off on restart | Verify `--auth` / `NEEV_AUTH`, clear browser cache. |
|
|
547
|
+
| `403 Forbidden` on a file that exists | Path-traversal guard / symlink escape | The file isn't inside the served directory's real path. |
|
|
548
|
+
| ZIP download returns `413` | Folder exceeds `--max-zip-size` | Raise the cap or download individual files. |
|
|
549
|
+
| Uploads return `405` | `--enable-upload` not set, or `--read-only` on | Enable uploads and disable read-only. |
|
|
550
|
+
| Dotfiles don't appear | Hidden by default | Use `--show-hidden`. |
|
|
551
|
+
| `neev.toml` takes effect unexpectedly | Merged from served directory | Check startup banner; override with CLI flags. |
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## FAQ
|
|
556
|
+
|
|
557
|
+
**Does neev support HTTPS?**
|
|
558
|
+
No. Use a reverse proxy (Caddy, nginx, Traefik) for TLS termination.
|
|
559
|
+
|
|
560
|
+
**Can I serve multiple directories?**
|
|
561
|
+
Not in one process. Run multiple `neev` instances on different ports.
|
|
562
|
+
|
|
563
|
+
**Is it safe to expose to the internet?**
|
|
564
|
+
Only behind HTTPS + auth + ideally `--read-only`. For anything mission-critical, reach for a proper server.
|
|
565
|
+
|
|
566
|
+
**Why "neev"?**
|
|
567
|
+
"Neev" (नींव) means *foundation* in Hindi — a simple base to build file sharing on.
|
|
568
|
+
|
|
569
|
+
**Does it work on Windows?**
|
|
570
|
+
Yes. Python 3.11+ required.
|
|
571
|
+
|
|
572
|
+
**What about WebDAV / S3 / FTP?**
|
|
573
|
+
Out of scope. neev is HTTP + browser UI only.
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Contributing
|
|
578
|
+
|
|
579
|
+
Issues and PRs welcome at [github.com/prabhuakshay/neev](https://github.com/prabhuakshay/neev).
|
|
580
|
+
|
|
581
|
+
Before submitting:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
uv run ruff check . && uv run ruff format --check .
|
|
585
|
+
uv run ty check
|
|
586
|
+
uv run pytest
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
See [CLAUDE.md](CLAUDE.md) for the full development philosophy (stability > features, stdlib only, files < 300 lines, etc.).
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## License
|
|
594
|
+
|
|
595
|
+
[MIT](LICENSE) © Akshay Prabhu
|