qrdrop 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.
- qrdrop-0.1.0/.gitignore +73 -0
- qrdrop-0.1.0/LICENSE +21 -0
- qrdrop-0.1.0/PKG-INFO +213 -0
- qrdrop-0.1.0/README.md +160 -0
- qrdrop-0.1.0/pyproject.toml +129 -0
- qrdrop-0.1.0/src/qrdrop/__init__.py +3 -0
- qrdrop-0.1.0/src/qrdrop/__main__.py +6 -0
- qrdrop-0.1.0/src/qrdrop/cli.py +164 -0
- qrdrop-0.1.0/src/qrdrop/core/__init__.py +1 -0
- qrdrop-0.1.0/src/qrdrop/core/filesystem.py +331 -0
- qrdrop-0.1.0/src/qrdrop/core/filetypes.py +367 -0
- qrdrop-0.1.0/src/qrdrop/core/log_redaction.py +50 -0
- qrdrop-0.1.0/src/qrdrop/core/network.py +72 -0
- qrdrop-0.1.0/src/qrdrop/core/password.py +49 -0
- qrdrop-0.1.0/src/qrdrop/core/qr.py +65 -0
- qrdrop-0.1.0/src/qrdrop/core/session.py +110 -0
- qrdrop-0.1.0/src/qrdrop/core/terminal.py +158 -0
- qrdrop-0.1.0/src/qrdrop/core/wordlist.py +1047 -0
- qrdrop-0.1.0/src/qrdrop/static/app.js +1098 -0
- qrdrop-0.1.0/src/qrdrop/static/style.css +1504 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/LICENSE +29 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/highlight.min.js +1213 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/cmake.min.js +7 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/dockerfile.min.js +8 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/fsharp.min.js +47 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/groovy.min.js +21 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/latex.min.js +33 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/powershell.min.js +39 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/properties.min.js +10 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/scala.min.js +28 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/languages/vim.min.js +12 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/styles/github-dark.min.css +10 -0
- qrdrop-0.1.0/src/qrdrop/static/vendor/highlight/styles/github.min.css +10 -0
- qrdrop-0.1.0/src/qrdrop/templates/base.html +52 -0
- qrdrop-0.1.0/src/qrdrop/templates/browse.html +224 -0
- qrdrop-0.1.0/src/qrdrop/templates/login.html +45 -0
- qrdrop-0.1.0/src/qrdrop/templates/view_image.html +39 -0
- qrdrop-0.1.0/src/qrdrop/templates/view_text.html +60 -0
- qrdrop-0.1.0/src/qrdrop/web/__init__.py +1 -0
- qrdrop-0.1.0/src/qrdrop/web/app.py +50 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/__init__.py +1 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/_common.py +104 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/archive.py +293 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/auth.py +129 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/browse.py +160 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/files.py +266 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/health.py +16 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/mutate.py +195 -0
- qrdrop-0.1.0/src/qrdrop/web/handlers/upload.py +398 -0
- qrdrop-0.1.0/src/qrdrop/web/middleware.py +180 -0
- qrdrop-0.1.0/src/qrdrop/web/routes.py +53 -0
- qrdrop-0.1.0/tests/__init__.py +1 -0
- qrdrop-0.1.0/tests/conftest.py +114 -0
- qrdrop-0.1.0/tests/e2e/__init__.py +1 -0
- qrdrop-0.1.0/tests/e2e/conftest.py +133 -0
- qrdrop-0.1.0/tests/e2e/test_smoke.py +44 -0
- qrdrop-0.1.0/tests/test_archive.py +141 -0
- qrdrop-0.1.0/tests/test_auth_middleware.py +253 -0
- qrdrop-0.1.0/tests/test_browse.py +110 -0
- qrdrop-0.1.0/tests/test_cli.py +70 -0
- qrdrop-0.1.0/tests/test_cli_main.py +209 -0
- qrdrop-0.1.0/tests/test_delete.py +79 -0
- qrdrop-0.1.0/tests/test_files.py +321 -0
- qrdrop-0.1.0/tests/test_filesystem.py +210 -0
- qrdrop-0.1.0/tests/test_filetypes.py +118 -0
- qrdrop-0.1.0/tests/test_health.py +15 -0
- qrdrop-0.1.0/tests/test_log_redaction.py +108 -0
- qrdrop-0.1.0/tests/test_mutate.py +212 -0
- qrdrop-0.1.0/tests/test_network.py +52 -0
- qrdrop-0.1.0/tests/test_password.py +50 -0
- qrdrop-0.1.0/tests/test_qr.py +114 -0
- qrdrop-0.1.0/tests/test_security.py +252 -0
- qrdrop-0.1.0/tests/test_session.py +89 -0
- qrdrop-0.1.0/tests/test_terminal.py +221 -0
- qrdrop-0.1.0/tests/test_upload.py +190 -0
- qrdrop-0.1.0/tests/test_upload_edge.py +208 -0
- qrdrop-0.1.0/tests/test_view.py +124 -0
- qrdrop-0.1.0/tests/test_wordlist.py +38 -0
qrdrop-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.installed.cfg
|
|
10
|
+
*.eggs
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
ENV/
|
|
16
|
+
|
|
17
|
+
# Pixi
|
|
18
|
+
.pixi/
|
|
19
|
+
|
|
20
|
+
# Build outputs
|
|
21
|
+
dist/
|
|
22
|
+
build/
|
|
23
|
+
wheels/
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.coverage
|
|
27
|
+
.coverage.*
|
|
28
|
+
htmlcov/
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.tox/
|
|
31
|
+
.nox/
|
|
32
|
+
|
|
33
|
+
# Linting/Formatting
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
|
|
37
|
+
# IDE
|
|
38
|
+
.idea/
|
|
39
|
+
.vscode/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
|
|
43
|
+
# Environment files (never commit secrets)
|
|
44
|
+
.env
|
|
45
|
+
.env.local
|
|
46
|
+
.env.*.local
|
|
47
|
+
|
|
48
|
+
# Logs
|
|
49
|
+
*.log
|
|
50
|
+
|
|
51
|
+
# OS files
|
|
52
|
+
.DS_Store
|
|
53
|
+
Thumbs.db
|
|
54
|
+
# Environment files (added by millhouse)
|
|
55
|
+
*.env
|
|
56
|
+
*.env.*
|
|
57
|
+
*.local
|
|
58
|
+
*.secret
|
|
59
|
+
*.secrets
|
|
60
|
+
config.local.*
|
|
61
|
+
.config.local
|
|
62
|
+
*.key
|
|
63
|
+
*.pem
|
|
64
|
+
*.p12
|
|
65
|
+
.aws/credentials
|
|
66
|
+
.ssh/id_*
|
|
67
|
+
|
|
68
|
+
# Agent tooling (machine-specific, never tracked)
|
|
69
|
+
.claude/
|
|
70
|
+
.lab/
|
|
71
|
+
AGENTS.md
|
|
72
|
+
CLAUDE.md
|
|
73
|
+
|
qrdrop-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 itsloopyo
|
|
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 OF OR IN CONNECTION WITH
|
|
21
|
+
THE SOFTWARE.
|
qrdrop-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qrdrop
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant file sharing from your terminal - share files over LAN with a simple command
|
|
5
|
+
Project-URL: Homepage, https://github.com/itsloopyo/qrdrop
|
|
6
|
+
Project-URL: Documentation, https://github.com/itsloopyo/qrdrop#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/itsloopyo/qrdrop
|
|
8
|
+
Project-URL: Issues, https://github.com/itsloopyo/qrdrop/issues
|
|
9
|
+
Author: itsloopyo
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,file-sharing,http,lan,server
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Environment :: Web Environment
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: System Administrators
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
25
|
+
Classifier: Topic :: System :: Networking
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Requires-Dist: aiofiles==25.1.0
|
|
29
|
+
Requires-Dist: anyio==4.13.0
|
|
30
|
+
Requires-Dist: click==8.3.3
|
|
31
|
+
Requires-Dist: colorama==0.4.6; sys_platform == 'win32' or platform_system == 'Windows'
|
|
32
|
+
Requires-Dist: h11==0.16.0
|
|
33
|
+
Requires-Dist: idna==3.15
|
|
34
|
+
Requires-Dist: jinja2==3.1.6
|
|
35
|
+
Requires-Dist: markupsafe==3.0.3
|
|
36
|
+
Requires-Dist: python-multipart==0.0.27
|
|
37
|
+
Requires-Dist: qrcode==8.2
|
|
38
|
+
Requires-Dist: starlette==1.0.1
|
|
39
|
+
Requires-Dist: typing-extensions==4.15.0; python_version < '3.13'
|
|
40
|
+
Requires-Dist: uvicorn==0.46.0
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: hatchling>=1.25.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest-playwright>=0.5.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# QRDrop
|
|
55
|
+
|
|
56
|
+
**Instant file sharing from your terminal.** Serve the current directory over your local network with one command — get a URL, a memorable password, and a QR code your phone can scan in moments.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uvx qrdrop
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
No setup, no fuss, ctrl+c and it's gone.
|
|
63
|
+
|
|
64
|
+
## Getting Started
|
|
65
|
+
|
|
66
|
+
Run it without installing anything:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uvx qrdrop # Python 3.11+ via uv
|
|
70
|
+
docker run --rm -p 8000:8000 -v "$PWD:/data" itsloopyo/qrdrop # no Python at all
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or install it (Python 3.11+):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install qrdrop
|
|
77
|
+
pipx install qrdrop
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See [Docker](#docker) below for port mapping, flags, and image details.
|
|
81
|
+
|
|
82
|
+
### 1. Share a directory
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd ~/photos
|
|
86
|
+
uvx qrdrop
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
╭──────────────────────────────────────────────────╮
|
|
91
|
+
│ 📂 QRDrop v0.1.0 │
|
|
92
|
+
╰──────────────────────────────────────────────────╯
|
|
93
|
+
|
|
94
|
+
Serving: /home/you/photos
|
|
95
|
+
|
|
96
|
+
Local: http://localhost:8000
|
|
97
|
+
Network: http://192.168.1.42:8000
|
|
98
|
+
|
|
99
|
+
Password: ember-velvet-canyon
|
|
100
|
+
|
|
101
|
+
┌─ Scan for instant access ─┐
|
|
102
|
+
|
|
103
|
+
▄▄▄▄▄▄▄ ▄ ▄▄▄▄ ▄▄▄▄▄▄▄
|
|
104
|
+
█ ▄▄▄ █ █▄ ▀█▀ ▀ █ ▄▄▄ █
|
|
105
|
+
█ ███ █ ▀██ ▄▄▀▄▀ █ ███ █
|
|
106
|
+
█▄▄▄▄▄█ █▀█ ▄▀█ █ █▄▄▄▄▄█
|
|
107
|
+
▄▄ ▄▄▄ █▀ ▀▀ ▄ ▄▄▄▄
|
|
108
|
+
█▀▀▀ ▄▄█▄▄▄▄█▄██ ▄▄▀█▄▀
|
|
109
|
+
▄▀▄▀█▄▄▄▀ ▄ █ ▀▄█ ▀▄██▄
|
|
110
|
+
▀▀ ▀▄▄▀▀█▀█ ▀▄██▀ ▄█▄▄▀
|
|
111
|
+
▄▄▀█▄▀▄▄▀█▀█ ▄▄▄▄██▄█▀
|
|
112
|
+
▄▄▄▄▄▄▄ ▀▀█▄▄█▄ █ ▄ █
|
|
113
|
+
█ ▄▄▄ █ ██ █ █▀█▄▄▄████
|
|
114
|
+
█ ███ █ ▄▄█▄ ▀▄▀█▀ ▄▀█▀
|
|
115
|
+
█▄▄▄▄▄█ █▀▄ ▀▄▀▄ ▄▀▀▀██▄
|
|
116
|
+
|
|
117
|
+
└───────────────────────────┘
|
|
118
|
+
|
|
119
|
+
Press Ctrl+C to stop the server
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If port 8000 is busy, QRDrop automatically picks the next free one.
|
|
123
|
+
|
|
124
|
+
### 2. Connect from another device
|
|
125
|
+
|
|
126
|
+
- **Phone**: scan the QR code. It encodes a pre-authenticated link, so you land in the file browser with no typing.
|
|
127
|
+
- **Anything else**: open the network URL and enter the three-word password.
|
|
128
|
+
|
|
129
|
+
### 3. Browse, view, download
|
|
130
|
+
|
|
131
|
+
The web UI lets you:
|
|
132
|
+
|
|
133
|
+
- **Browse** directories with breadcrumb navigation
|
|
134
|
+
- **View** files inline — syntax-highlighted code (Python, Rust, Go, TypeScript, shell, and dozens more), images, and PDFs
|
|
135
|
+
- **Download** any single file, or select several and grab them as one **ZIP, TAR.GZ, or TAR.BZ2** archive
|
|
136
|
+
|
|
137
|
+
### 4. Allow changes (optional)
|
|
138
|
+
|
|
139
|
+
QRDrop is **read-only by default**. Opt in to more:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
qrdrop --upload # downloads and uploads only
|
|
143
|
+
qrdrop --modify # downloads, uploads, delete, new folders + rename
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Usage Examples
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
qrdrop --port 9000 # custom port
|
|
150
|
+
qrdrop --password correct-horse # bring your own password
|
|
151
|
+
qrdrop --hide-dotfiles # exclude dotfiles from listings
|
|
152
|
+
qrdrop --bind 192.168.1.42 # bind one interface (default: 0.0.0.0)
|
|
153
|
+
qrdrop --timeout 7200 # expire sessions after 2 hours (default: never)
|
|
154
|
+
qrdrop --quiet # no banner, warnings-only logs
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Note that QRDrop lists everything by default, including dotfiles. `--hide-dotfiles` is the opt-out.
|
|
158
|
+
|
|
159
|
+
## CLI Reference
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
qrdrop [OPTIONS]
|
|
163
|
+
|
|
164
|
+
Options:
|
|
165
|
+
-p, --port PORT Port to serve on (default: 8000)
|
|
166
|
+
-b, --bind ADDRESS Address to bind to (default: 0.0.0.0)
|
|
167
|
+
--password TEXT Use specific password instead of generating one
|
|
168
|
+
--hide-dotfiles Exclude files starting with '.' from listings
|
|
169
|
+
--upload Allow file uploads
|
|
170
|
+
--modify Allow uploads, deletions, and directory create/rename (implies --upload)
|
|
171
|
+
--timeout SECONDS Expire sessions after this many seconds (default: sessions
|
|
172
|
+
last until the server stops)
|
|
173
|
+
-q, --quiet Suppress startup banner
|
|
174
|
+
--version Show version and exit
|
|
175
|
+
--help Show help and exit
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Docker
|
|
179
|
+
|
|
180
|
+
Images are published to Docker Hub as [`itsloopyo/qrdrop`](https://hub.docker.com/r/itsloopyo/qrdrop) on every release. Mount the directory you want to share at `/data` and forward a port to 8000:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
docker run --rm -p 8000:8000 -v /path/to/share:/data itsloopyo/qrdrop
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The startup banner — including the generated password and QR code — goes to the container logs, so run in the foreground or check `docker logs`. Any `qrdrop` flags can be appended:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
docker run --rm -p 9000:8000 -v "$PWD:/data" itsloopyo/qrdrop --password correct-horse --modify
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The container always listens on port 8000 internally; pick your external port with `-p <port>:8000` rather than `--port`. The QR code and network URL in the banner show the container's internal address — from another device, use your host's IP and the port you published.
|
|
193
|
+
|
|
194
|
+
The image is multi-stage (uv on Alpine), runs as a non-root user, and has a built-in healthcheck against `/health`. To share read-write (with `--upload` or `--modify`), the mounted directory must be writable by uid 1000.
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
The dev loop is [pixi](https://pixi.sh):
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pixi run build # editable install with dev extras
|
|
202
|
+
pixi run test # pytest with coverage
|
|
203
|
+
pixi run test-e2e # Playwright end-to-end tests
|
|
204
|
+
pixi run lint # ruff check
|
|
205
|
+
pixi run format # ruff format
|
|
206
|
+
pixi run dev # run qrdrop from source
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
QRDrop is built on Starlette, Uvicorn, Jinja2, and aiofiles.
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
qrdrop-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# QRDrop
|
|
2
|
+
|
|
3
|
+
**Instant file sharing from your terminal.** Serve the current directory over your local network with one command — get a URL, a memorable password, and a QR code your phone can scan in moments.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uvx qrdrop
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
No setup, no fuss, ctrl+c and it's gone.
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
Run it without installing anything:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uvx qrdrop # Python 3.11+ via uv
|
|
17
|
+
docker run --rm -p 8000:8000 -v "$PWD:/data" itsloopyo/qrdrop # no Python at all
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or install it (Python 3.11+):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install qrdrop
|
|
24
|
+
pipx install qrdrop
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See [Docker](#docker) below for port mapping, flags, and image details.
|
|
28
|
+
|
|
29
|
+
### 1. Share a directory
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd ~/photos
|
|
33
|
+
uvx qrdrop
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
╭──────────────────────────────────────────────────╮
|
|
38
|
+
│ 📂 QRDrop v0.1.0 │
|
|
39
|
+
╰──────────────────────────────────────────────────╯
|
|
40
|
+
|
|
41
|
+
Serving: /home/you/photos
|
|
42
|
+
|
|
43
|
+
Local: http://localhost:8000
|
|
44
|
+
Network: http://192.168.1.42:8000
|
|
45
|
+
|
|
46
|
+
Password: ember-velvet-canyon
|
|
47
|
+
|
|
48
|
+
┌─ Scan for instant access ─┐
|
|
49
|
+
|
|
50
|
+
▄▄▄▄▄▄▄ ▄ ▄▄▄▄ ▄▄▄▄▄▄▄
|
|
51
|
+
█ ▄▄▄ █ █▄ ▀█▀ ▀ █ ▄▄▄ █
|
|
52
|
+
█ ███ █ ▀██ ▄▄▀▄▀ █ ███ █
|
|
53
|
+
█▄▄▄▄▄█ █▀█ ▄▀█ █ █▄▄▄▄▄█
|
|
54
|
+
▄▄ ▄▄▄ █▀ ▀▀ ▄ ▄▄▄▄
|
|
55
|
+
█▀▀▀ ▄▄█▄▄▄▄█▄██ ▄▄▀█▄▀
|
|
56
|
+
▄▀▄▀█▄▄▄▀ ▄ █ ▀▄█ ▀▄██▄
|
|
57
|
+
▀▀ ▀▄▄▀▀█▀█ ▀▄██▀ ▄█▄▄▀
|
|
58
|
+
▄▄▀█▄▀▄▄▀█▀█ ▄▄▄▄██▄█▀
|
|
59
|
+
▄▄▄▄▄▄▄ ▀▀█▄▄█▄ █ ▄ █
|
|
60
|
+
█ ▄▄▄ █ ██ █ █▀█▄▄▄████
|
|
61
|
+
█ ███ █ ▄▄█▄ ▀▄▀█▀ ▄▀█▀
|
|
62
|
+
█▄▄▄▄▄█ █▀▄ ▀▄▀▄ ▄▀▀▀██▄
|
|
63
|
+
|
|
64
|
+
└───────────────────────────┘
|
|
65
|
+
|
|
66
|
+
Press Ctrl+C to stop the server
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If port 8000 is busy, QRDrop automatically picks the next free one.
|
|
70
|
+
|
|
71
|
+
### 2. Connect from another device
|
|
72
|
+
|
|
73
|
+
- **Phone**: scan the QR code. It encodes a pre-authenticated link, so you land in the file browser with no typing.
|
|
74
|
+
- **Anything else**: open the network URL and enter the three-word password.
|
|
75
|
+
|
|
76
|
+
### 3. Browse, view, download
|
|
77
|
+
|
|
78
|
+
The web UI lets you:
|
|
79
|
+
|
|
80
|
+
- **Browse** directories with breadcrumb navigation
|
|
81
|
+
- **View** files inline — syntax-highlighted code (Python, Rust, Go, TypeScript, shell, and dozens more), images, and PDFs
|
|
82
|
+
- **Download** any single file, or select several and grab them as one **ZIP, TAR.GZ, or TAR.BZ2** archive
|
|
83
|
+
|
|
84
|
+
### 4. Allow changes (optional)
|
|
85
|
+
|
|
86
|
+
QRDrop is **read-only by default**. Opt in to more:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
qrdrop --upload # downloads and uploads only
|
|
90
|
+
qrdrop --modify # downloads, uploads, delete, new folders + rename
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage Examples
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
qrdrop --port 9000 # custom port
|
|
97
|
+
qrdrop --password correct-horse # bring your own password
|
|
98
|
+
qrdrop --hide-dotfiles # exclude dotfiles from listings
|
|
99
|
+
qrdrop --bind 192.168.1.42 # bind one interface (default: 0.0.0.0)
|
|
100
|
+
qrdrop --timeout 7200 # expire sessions after 2 hours (default: never)
|
|
101
|
+
qrdrop --quiet # no banner, warnings-only logs
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Note that QRDrop lists everything by default, including dotfiles. `--hide-dotfiles` is the opt-out.
|
|
105
|
+
|
|
106
|
+
## CLI Reference
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
qrdrop [OPTIONS]
|
|
110
|
+
|
|
111
|
+
Options:
|
|
112
|
+
-p, --port PORT Port to serve on (default: 8000)
|
|
113
|
+
-b, --bind ADDRESS Address to bind to (default: 0.0.0.0)
|
|
114
|
+
--password TEXT Use specific password instead of generating one
|
|
115
|
+
--hide-dotfiles Exclude files starting with '.' from listings
|
|
116
|
+
--upload Allow file uploads
|
|
117
|
+
--modify Allow uploads, deletions, and directory create/rename (implies --upload)
|
|
118
|
+
--timeout SECONDS Expire sessions after this many seconds (default: sessions
|
|
119
|
+
last until the server stops)
|
|
120
|
+
-q, --quiet Suppress startup banner
|
|
121
|
+
--version Show version and exit
|
|
122
|
+
--help Show help and exit
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Docker
|
|
126
|
+
|
|
127
|
+
Images are published to Docker Hub as [`itsloopyo/qrdrop`](https://hub.docker.com/r/itsloopyo/qrdrop) on every release. Mount the directory you want to share at `/data` and forward a port to 8000:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
docker run --rm -p 8000:8000 -v /path/to/share:/data itsloopyo/qrdrop
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The startup banner — including the generated password and QR code — goes to the container logs, so run in the foreground or check `docker logs`. Any `qrdrop` flags can be appended:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
docker run --rm -p 9000:8000 -v "$PWD:/data" itsloopyo/qrdrop --password correct-horse --modify
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The container always listens on port 8000 internally; pick your external port with `-p <port>:8000` rather than `--port`. The QR code and network URL in the banner show the container's internal address — from another device, use your host's IP and the port you published.
|
|
140
|
+
|
|
141
|
+
The image is multi-stage (uv on Alpine), runs as a non-root user, and has a built-in healthcheck against `/health`. To share read-write (with `--upload` or `--modify`), the mounted directory must be writable by uid 1000.
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
The dev loop is [pixi](https://pixi.sh):
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pixi run build # editable install with dev extras
|
|
149
|
+
pixi run test # pytest with coverage
|
|
150
|
+
pixi run test-e2e # Playwright end-to-end tests
|
|
151
|
+
pixi run lint # ruff check
|
|
152
|
+
pixi run format # ruff format
|
|
153
|
+
pixi run dev # run qrdrop from source
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
QRDrop is built on Starlette, Uvicorn, Jinja2, and aiofiles.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qrdrop"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Instant file sharing from your terminal - share files over LAN with a simple command"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "itsloopyo" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["file-sharing", "lan", "http", "server", "cli"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Environment :: Web Environment",
|
|
16
|
+
"Framework :: AsyncIO",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: System Administrators",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
|
25
|
+
"Topic :: System :: Networking",
|
|
26
|
+
"Topic :: Utilities",
|
|
27
|
+
]
|
|
28
|
+
# The FULL transitive closure is pinned ==, deliberately: qrdrop is an
|
|
29
|
+
# application installed via `uvx qrdrop`/`pip install qrdrop`, so end users
|
|
30
|
+
# resolve from this metadata at install time. Exact pins mean they get the
|
|
31
|
+
# versions we tested, not whatever was published this morning - a hijacked
|
|
32
|
+
# dependency release cannot reach users without a new qrdrop release.
|
|
33
|
+
# Bump via `pixi run lock` (enforces the two-week release cooldown), retest,
|
|
34
|
+
# release.
|
|
35
|
+
dependencies = [
|
|
36
|
+
"starlette==1.0.1",
|
|
37
|
+
"uvicorn==0.46.0",
|
|
38
|
+
"jinja2==3.1.6",
|
|
39
|
+
"qrcode==8.2",
|
|
40
|
+
"python-multipart==0.0.27",
|
|
41
|
+
"aiofiles==25.1.0",
|
|
42
|
+
# Transitive
|
|
43
|
+
"anyio==4.13.0",
|
|
44
|
+
"click==8.3.3",
|
|
45
|
+
"colorama==0.4.6; sys_platform == 'win32' or platform_system == 'Windows'",
|
|
46
|
+
"h11==0.16.0",
|
|
47
|
+
"idna==3.15",
|
|
48
|
+
"markupsafe==3.0.3",
|
|
49
|
+
"typing-extensions==4.15.0; python_version < '3.13'",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
dev = [
|
|
54
|
+
"pytest>=8.0.0",
|
|
55
|
+
"pytest-asyncio>=0.24.0",
|
|
56
|
+
"pytest-cov>=5.0.0",
|
|
57
|
+
"ruff>=0.6.0",
|
|
58
|
+
"httpx>=0.27.0",
|
|
59
|
+
"pytest-playwright>=0.5.0",
|
|
60
|
+
"build>=1.0.0",
|
|
61
|
+
"twine>=5.0.0",
|
|
62
|
+
"hatchling>=1.25.0",
|
|
63
|
+
"pip-audit>=2.7.0",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[project.scripts]
|
|
67
|
+
qrdrop = "qrdrop.__main__:main"
|
|
68
|
+
|
|
69
|
+
[project.urls]
|
|
70
|
+
Homepage = "https://github.com/itsloopyo/qrdrop"
|
|
71
|
+
Documentation = "https://github.com/itsloopyo/qrdrop#readme"
|
|
72
|
+
Repository = "https://github.com/itsloopyo/qrdrop"
|
|
73
|
+
Issues = "https://github.com/itsloopyo/qrdrop/issues"
|
|
74
|
+
|
|
75
|
+
[build-system]
|
|
76
|
+
requires = ["hatchling"]
|
|
77
|
+
build-backend = "hatchling.build"
|
|
78
|
+
|
|
79
|
+
[tool.hatch.build.targets.wheel]
|
|
80
|
+
packages = ["src/qrdrop"]
|
|
81
|
+
|
|
82
|
+
[tool.hatch.build.targets.sdist]
|
|
83
|
+
include = [
|
|
84
|
+
"/src",
|
|
85
|
+
"/tests",
|
|
86
|
+
"/README.md",
|
|
87
|
+
"/LICENSE",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
testpaths = ["tests"]
|
|
92
|
+
asyncio_mode = "auto"
|
|
93
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
94
|
+
|
|
95
|
+
[tool.coverage.run]
|
|
96
|
+
source = ["src/qrdrop"]
|
|
97
|
+
branch = true
|
|
98
|
+
|
|
99
|
+
[tool.coverage.report]
|
|
100
|
+
exclude_lines = [
|
|
101
|
+
"pragma: no cover",
|
|
102
|
+
"if __name__ == .__main__.:",
|
|
103
|
+
"if TYPE_CHECKING:",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff]
|
|
107
|
+
target-version = "py311"
|
|
108
|
+
line-length = 100
|
|
109
|
+
src = ["src", "tests"]
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint]
|
|
112
|
+
select = [
|
|
113
|
+
"E", # pycodestyle errors
|
|
114
|
+
"W", # pycodestyle warnings
|
|
115
|
+
"F", # Pyflakes
|
|
116
|
+
"I", # isort
|
|
117
|
+
"B", # flake8-bugbear
|
|
118
|
+
"C4", # flake8-comprehensions
|
|
119
|
+
"UP", # pyupgrade
|
|
120
|
+
"ARG", # flake8-unused-arguments
|
|
121
|
+
"SIM", # flake8-simplify
|
|
122
|
+
]
|
|
123
|
+
ignore = [
|
|
124
|
+
"E501", # line too long (handled by formatter)
|
|
125
|
+
"B008", # do not perform function calls in argument defaults
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
[tool.ruff.lint.isort]
|
|
129
|
+
known-first-party = ["qrdrop"]
|