servepass 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.
- servepass-0.1.0/LICENSE +21 -0
- servepass-0.1.0/PKG-INFO +153 -0
- servepass-0.1.0/README.md +103 -0
- servepass-0.1.0/pyproject.toml +42 -0
- servepass-0.1.0/servepass/__init__.py +1 -0
- servepass-0.1.0/servepass/__main__.py +241 -0
- servepass-0.1.0/servepass.egg-info/PKG-INFO +153 -0
- servepass-0.1.0/servepass.egg-info/SOURCES.txt +10 -0
- servepass-0.1.0/servepass.egg-info/dependency_links.txt +1 -0
- servepass-0.1.0/servepass.egg-info/entry_points.txt +2 -0
- servepass-0.1.0/servepass.egg-info/top_level.txt +1 -0
- servepass-0.1.0/setup.cfg +4 -0
servepass-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fulvio Favilla
|
|
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.
|
servepass-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: servepass
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LAN file server with password protection. Like python -m http.server but with auth
|
|
5
|
+
Author-email: Fulvio Favilla <contact@fulvio.sh>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Fulvio Favilla
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/fulviofavilla/servepass
|
|
29
|
+
Project-URL: Repository, https://github.com/fulviofavilla/servepass
|
|
30
|
+
Project-URL: Issues, https://github.com/fulviofavilla/servepass/issues
|
|
31
|
+
Keywords: http,server,lan,file-sharing,auth,privacy
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Intended Audience :: System Administrators
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
44
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
45
|
+
Classifier: Topic :: Utilities
|
|
46
|
+
Requires-Python: >=3.8
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# servepass
|
|
52
|
+
|
|
53
|
+
Serve files on your LAN with a token. That's it.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pipx install servepass
|
|
57
|
+
servepass
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
You know `python -m http.server`. It's great for quick file sharing on a local network. The problem is it has zero authentication, so anyone on the same network can grab whatever you're serving.
|
|
63
|
+
|
|
64
|
+
`servepass` fixes that. Same zero-config philosophy, but with a token-protected URL. Run it, get a link, share it. No accounts, no cloud, no telemetry.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pipx install servepass
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or with pip:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install servepass
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Requires Python 3.8+. No external dependencies.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Serve current directory
|
|
84
|
+
servepass
|
|
85
|
+
|
|
86
|
+
# Serve a specific path
|
|
87
|
+
servepass ./downloads
|
|
88
|
+
|
|
89
|
+
# Custom port
|
|
90
|
+
servepass --port 9090
|
|
91
|
+
|
|
92
|
+
# No authentication
|
|
93
|
+
servepass --no-auth
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Every time you run servepass, a fresh token is generated. You get a URL and an optional QR code in the terminal:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Serving: /home/user/downloads
|
|
100
|
+
Network: http://192.168.1.42:8080/?token=a3f9b2c1
|
|
101
|
+
Token: a3f9b2c1
|
|
102
|
+
|
|
103
|
+
Ctrl+C to stop
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Open the URL in any browser on your network and you're in. Scan the QR code to open it directly on your phone.
|
|
107
|
+
|
|
108
|
+
## QR code
|
|
109
|
+
|
|
110
|
+
The QR code is printed automatically if the `qrcode` package is installed:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install qrcode
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Without it, servepass works normally and just shows the URL.
|
|
117
|
+
|
|
118
|
+
## Known limitations
|
|
119
|
+
|
|
120
|
+
servepass uses a token in the URL over plain HTTP. The token is not encrypted in transit. This is fine for a trusted local network. It is not a substitute for HTTPS or VPN on public or untrusted networks.
|
|
121
|
+
|
|
122
|
+
If you're on public WiFi, use something else.
|
|
123
|
+
|
|
124
|
+
## Who this is for
|
|
125
|
+
|
|
126
|
+
* You're on a trusted LAN (home, office, workshop) and want to share files quickly
|
|
127
|
+
* You want to download files on your phone without cables or cloud storage
|
|
128
|
+
* You don't want to expose an unauthenticated HTTP server to everyone on the network
|
|
129
|
+
* You want something lightweight with zero setup time
|
|
130
|
+
|
|
131
|
+
## Comparison
|
|
132
|
+
|
|
133
|
+
| | servepass | python -m http.server | filebrowser |
|
|
134
|
+
|---|---|---|---|
|
|
135
|
+
| Authentication | Token | No | Yes |
|
|
136
|
+
| Install | `pipx install servepass` | Built-in | Binary + config |
|
|
137
|
+
| Dependencies | None | None | None |
|
|
138
|
+
| LAN IP detection | Yes | No | Yes |
|
|
139
|
+
| QR code | Optional | No | No |
|
|
140
|
+
| File upload | Planned | No | Yes |
|
|
141
|
+
|
|
142
|
+
## Roadmap
|
|
143
|
+
|
|
144
|
+
- [x] Serve with token auth
|
|
145
|
+
- [x] Auto-detect LAN IP
|
|
146
|
+
- [x] QR code in terminal (optional)
|
|
147
|
+
- [ ] `--token` flag to set a fixed token
|
|
148
|
+
- [ ] HTTPS with self-signed cert
|
|
149
|
+
- [ ] File upload via browser
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# servepass
|
|
2
|
+
|
|
3
|
+
Serve files on your LAN with a token. That's it.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pipx install servepass
|
|
7
|
+
servepass
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You know `python -m http.server`. It's great for quick file sharing on a local network. The problem is it has zero authentication, so anyone on the same network can grab whatever you're serving.
|
|
13
|
+
|
|
14
|
+
`servepass` fixes that. Same zero-config philosophy, but with a token-protected URL. Run it, get a link, share it. No accounts, no cloud, no telemetry.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pipx install servepass
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or with pip:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install servepass
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.8+. No external dependencies.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Serve current directory
|
|
34
|
+
servepass
|
|
35
|
+
|
|
36
|
+
# Serve a specific path
|
|
37
|
+
servepass ./downloads
|
|
38
|
+
|
|
39
|
+
# Custom port
|
|
40
|
+
servepass --port 9090
|
|
41
|
+
|
|
42
|
+
# No authentication
|
|
43
|
+
servepass --no-auth
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every time you run servepass, a fresh token is generated. You get a URL and an optional QR code in the terminal:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Serving: /home/user/downloads
|
|
50
|
+
Network: http://192.168.1.42:8080/?token=a3f9b2c1
|
|
51
|
+
Token: a3f9b2c1
|
|
52
|
+
|
|
53
|
+
Ctrl+C to stop
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Open the URL in any browser on your network and you're in. Scan the QR code to open it directly on your phone.
|
|
57
|
+
|
|
58
|
+
## QR code
|
|
59
|
+
|
|
60
|
+
The QR code is printed automatically if the `qrcode` package is installed:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install qrcode
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Without it, servepass works normally and just shows the URL.
|
|
67
|
+
|
|
68
|
+
## Known limitations
|
|
69
|
+
|
|
70
|
+
servepass uses a token in the URL over plain HTTP. The token is not encrypted in transit. This is fine for a trusted local network. It is not a substitute for HTTPS or VPN on public or untrusted networks.
|
|
71
|
+
|
|
72
|
+
If you're on public WiFi, use something else.
|
|
73
|
+
|
|
74
|
+
## Who this is for
|
|
75
|
+
|
|
76
|
+
* You're on a trusted LAN (home, office, workshop) and want to share files quickly
|
|
77
|
+
* You want to download files on your phone without cables or cloud storage
|
|
78
|
+
* You don't want to expose an unauthenticated HTTP server to everyone on the network
|
|
79
|
+
* You want something lightweight with zero setup time
|
|
80
|
+
|
|
81
|
+
## Comparison
|
|
82
|
+
|
|
83
|
+
| | servepass | python -m http.server | filebrowser |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| Authentication | Token | No | Yes |
|
|
86
|
+
| Install | `pipx install servepass` | Built-in | Binary + config |
|
|
87
|
+
| Dependencies | None | None | None |
|
|
88
|
+
| LAN IP detection | Yes | No | Yes |
|
|
89
|
+
| QR code | Optional | No | No |
|
|
90
|
+
| File upload | Planned | No | Yes |
|
|
91
|
+
|
|
92
|
+
## Roadmap
|
|
93
|
+
|
|
94
|
+
- [x] Serve with token auth
|
|
95
|
+
- [x] Auto-detect LAN IP
|
|
96
|
+
- [x] QR code in terminal (optional)
|
|
97
|
+
- [ ] `--token` flag to set a fixed token
|
|
98
|
+
- [ ] HTTPS with self-signed cert
|
|
99
|
+
- [ ] File upload via browser
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "servepass"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "LAN file server with password protection. Like python -m http.server but with auth"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "Fulvio Favilla", email = "contact@fulvio.sh" }]
|
|
12
|
+
keywords = ["http", "server", "lan", "file-sharing", "auth", "privacy"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
requires-python = ">=3.8"
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/fulviofavilla/servepass"
|
|
34
|
+
Repository = "https://github.com/fulviofavilla/servepass"
|
|
35
|
+
Issues = "https://github.com/fulviofavilla/servepass/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
servepass = "servepass.__main__:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["."]
|
|
42
|
+
include = ["servepass*"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
servepass -- serve files on the LAN with token auth.
|
|
3
|
+
|
|
4
|
+
Nothing fancy. No dependencies. Just stdlib and a link.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import secrets
|
|
11
|
+
import signal
|
|
12
|
+
import socket
|
|
13
|
+
import sys
|
|
14
|
+
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from urllib.parse import urlparse, parse_qs
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
CONFIG_DIR = Path.home() / ".servepass"
|
|
20
|
+
CONFIG_FILE = CONFIG_DIR / "config"
|
|
21
|
+
DEFAULT_PORT = 8080
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# QR code -- pure stdlib, no external deps
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
def _qr_terminal(url):
|
|
29
|
+
"""
|
|
30
|
+
Print a QR code for the given URL using Unicode block characters.
|
|
31
|
+
Requires the 'qrcode' package. If not installed, silently skips.
|
|
32
|
+
This is intentionally optional -- the URL is always printed anyway.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
import qrcode
|
|
36
|
+
qr = qrcode.QRCode(border=1)
|
|
37
|
+
qr.add_data(url)
|
|
38
|
+
qr.make(fit=True)
|
|
39
|
+
matrix = qr.get_matrix()
|
|
40
|
+
print()
|
|
41
|
+
for i in range(0, len(matrix) - 1, 2):
|
|
42
|
+
row = " "
|
|
43
|
+
for j in range(len(matrix[i])):
|
|
44
|
+
top = matrix[i][j]
|
|
45
|
+
bot = matrix[i + 1][j] if i + 1 < len(matrix) else False
|
|
46
|
+
if top and bot:
|
|
47
|
+
row += "\u2588" # full block
|
|
48
|
+
elif top:
|
|
49
|
+
row += "\u2580" # upper half
|
|
50
|
+
elif bot:
|
|
51
|
+
row += "\u2584" # lower half
|
|
52
|
+
else:
|
|
53
|
+
row += " "
|
|
54
|
+
print(row)
|
|
55
|
+
print()
|
|
56
|
+
except ImportError:
|
|
57
|
+
# qrcode not installed, no problem -- URL is printed in the banner
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Network
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def get_lan_ip():
|
|
66
|
+
"""Best-effort local IP detection. Falls back to 127.0.0.1 if it fails."""
|
|
67
|
+
try:
|
|
68
|
+
# connect to an external address but don't actually send anything
|
|
69
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
70
|
+
s.connect(("8.8.8.8", 80))
|
|
71
|
+
ip = s.getsockname()[0]
|
|
72
|
+
s.close()
|
|
73
|
+
return ip
|
|
74
|
+
except Exception:
|
|
75
|
+
return "127.0.0.1"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# Token generation
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
def generate_token():
|
|
83
|
+
"""8 hex chars -- short enough to type, long enough to not guess."""
|
|
84
|
+
return secrets.token_hex(4)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Request handlers
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def make_token_handler(token, directory):
|
|
92
|
+
"""Handler that checks for ?token=<token> on every request."""
|
|
93
|
+
|
|
94
|
+
class TokenHandler(SimpleHTTPRequestHandler):
|
|
95
|
+
def __init__(self, *args, **kwargs):
|
|
96
|
+
super().__init__(*args, directory=directory, **kwargs)
|
|
97
|
+
|
|
98
|
+
def do_HEAD(self):
|
|
99
|
+
if not self.check_token():
|
|
100
|
+
return
|
|
101
|
+
super().do_HEAD()
|
|
102
|
+
|
|
103
|
+
def do_GET(self):
|
|
104
|
+
if not self.check_token():
|
|
105
|
+
return
|
|
106
|
+
# strip token from path before serving so directory listing works
|
|
107
|
+
self.path = self._strip_token(self.path)
|
|
108
|
+
super().do_GET()
|
|
109
|
+
|
|
110
|
+
def check_token(self):
|
|
111
|
+
parsed = urlparse(self.path)
|
|
112
|
+
params = parse_qs(parsed.query)
|
|
113
|
+
supplied = params.get("token", [None])[0]
|
|
114
|
+
if supplied == token:
|
|
115
|
+
return True
|
|
116
|
+
self.send_response(403)
|
|
117
|
+
self.send_header("Content-Type", "text/plain")
|
|
118
|
+
self.end_headers()
|
|
119
|
+
self.wfile.write(b"Access denied. Missing or invalid token.")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def _strip_token(self, path):
|
|
123
|
+
"""Remove ?token=... from path so links in directory listing stay clean."""
|
|
124
|
+
parsed = urlparse(path)
|
|
125
|
+
params = parse_qs(parsed.query)
|
|
126
|
+
params.pop("token", None)
|
|
127
|
+
# rebuild query string without token
|
|
128
|
+
new_query = "&".join(f"{k}={v[0]}" for k, v in params.items())
|
|
129
|
+
return parsed.path + (f"?{new_query}" if new_query else "")
|
|
130
|
+
|
|
131
|
+
def log_message(self, format, *args):
|
|
132
|
+
print(f" {self.address_string()} {args[0]} {args[1]}")
|
|
133
|
+
|
|
134
|
+
return TokenHandler
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def make_open_handler(directory):
|
|
138
|
+
"""Handler with no auth, for --no-auth mode."""
|
|
139
|
+
|
|
140
|
+
class OpenHandler(SimpleHTTPRequestHandler):
|
|
141
|
+
def __init__(self, *args, **kwargs):
|
|
142
|
+
super().__init__(*args, directory=directory, **kwargs)
|
|
143
|
+
|
|
144
|
+
def log_message(self, format, *args):
|
|
145
|
+
print(f" {self.address_string()} {args[0]} {args[1]}")
|
|
146
|
+
|
|
147
|
+
return OpenHandler
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Banner
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def print_banner(directory, ip, port, token):
|
|
155
|
+
url = f"http://{ip}:{port}"
|
|
156
|
+
if token:
|
|
157
|
+
full_url = f"{url}/?token={token}"
|
|
158
|
+
print()
|
|
159
|
+
print(f" Serving: {os.path.abspath(directory)}")
|
|
160
|
+
print(f" Network: {full_url}")
|
|
161
|
+
print(f" Token: {token}")
|
|
162
|
+
_qr_terminal(full_url)
|
|
163
|
+
else:
|
|
164
|
+
print()
|
|
165
|
+
print(f" Serving: {os.path.abspath(directory)}")
|
|
166
|
+
print(f" Network: {url}")
|
|
167
|
+
print(f" Auth: disabled (--no-auth)")
|
|
168
|
+
print()
|
|
169
|
+
|
|
170
|
+
print(" Ctrl+C to stop")
|
|
171
|
+
print()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Entry point
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
def main():
|
|
179
|
+
parser = argparse.ArgumentParser(
|
|
180
|
+
prog="servepass",
|
|
181
|
+
description="Serve files on the LAN with token auth.",
|
|
182
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
183
|
+
epilog="""
|
|
184
|
+
examples:
|
|
185
|
+
servepass serve current directory
|
|
186
|
+
servepass ./downloads serve a specific directory
|
|
187
|
+
servepass --port 9090 use a custom port
|
|
188
|
+
servepass --no-auth serve without authentication
|
|
189
|
+
""",
|
|
190
|
+
)
|
|
191
|
+
parser.add_argument(
|
|
192
|
+
"dir",
|
|
193
|
+
nargs="?",
|
|
194
|
+
default=".",
|
|
195
|
+
metavar="DIR",
|
|
196
|
+
help="directory to serve (default: current directory)",
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"--port",
|
|
200
|
+
type=int,
|
|
201
|
+
default=DEFAULT_PORT,
|
|
202
|
+
help=f"port to listen on (default: {DEFAULT_PORT})",
|
|
203
|
+
)
|
|
204
|
+
parser.add_argument(
|
|
205
|
+
"--no-auth",
|
|
206
|
+
action="store_true",
|
|
207
|
+
help="disable authentication",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
args = parser.parse_args()
|
|
211
|
+
|
|
212
|
+
directory = args.dir
|
|
213
|
+
if not os.path.isdir(directory):
|
|
214
|
+
print(f"servepass: '{directory}' is not a directory", file=sys.stderr)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
use_auth = not args.no_auth
|
|
218
|
+
token = generate_token() if use_auth else None
|
|
219
|
+
ip = get_lan_ip()
|
|
220
|
+
|
|
221
|
+
if use_auth:
|
|
222
|
+
handler = make_token_handler(token=token, directory=directory)
|
|
223
|
+
else:
|
|
224
|
+
handler = make_open_handler(directory=directory)
|
|
225
|
+
|
|
226
|
+
server = HTTPServer(("0.0.0.0", args.port), handler)
|
|
227
|
+
|
|
228
|
+
def handle_exit(sig, frame):
|
|
229
|
+
print("\n\n Stopped.\n")
|
|
230
|
+
server.server_close()
|
|
231
|
+
sys.exit(0)
|
|
232
|
+
|
|
233
|
+
signal.signal(signal.SIGINT, handle_exit)
|
|
234
|
+
signal.signal(signal.SIGTERM, handle_exit)
|
|
235
|
+
|
|
236
|
+
print_banner(directory, ip, args.port, token)
|
|
237
|
+
server.serve_forever()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == "__main__":
|
|
241
|
+
main()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: servepass
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LAN file server with password protection. Like python -m http.server but with auth
|
|
5
|
+
Author-email: Fulvio Favilla <contact@fulvio.sh>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Fulvio Favilla
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/fulviofavilla/servepass
|
|
29
|
+
Project-URL: Repository, https://github.com/fulviofavilla/servepass
|
|
30
|
+
Project-URL: Issues, https://github.com/fulviofavilla/servepass/issues
|
|
31
|
+
Keywords: http,server,lan,file-sharing,auth,privacy
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Intended Audience :: System Administrators
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
44
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
45
|
+
Classifier: Topic :: Utilities
|
|
46
|
+
Requires-Python: >=3.8
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# servepass
|
|
52
|
+
|
|
53
|
+
Serve files on your LAN with a token. That's it.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pipx install servepass
|
|
57
|
+
servepass
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
You know `python -m http.server`. It's great for quick file sharing on a local network. The problem is it has zero authentication, so anyone on the same network can grab whatever you're serving.
|
|
63
|
+
|
|
64
|
+
`servepass` fixes that. Same zero-config philosophy, but with a token-protected URL. Run it, get a link, share it. No accounts, no cloud, no telemetry.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pipx install servepass
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or with pip:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install servepass
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Requires Python 3.8+. No external dependencies.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Serve current directory
|
|
84
|
+
servepass
|
|
85
|
+
|
|
86
|
+
# Serve a specific path
|
|
87
|
+
servepass ./downloads
|
|
88
|
+
|
|
89
|
+
# Custom port
|
|
90
|
+
servepass --port 9090
|
|
91
|
+
|
|
92
|
+
# No authentication
|
|
93
|
+
servepass --no-auth
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Every time you run servepass, a fresh token is generated. You get a URL and an optional QR code in the terminal:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Serving: /home/user/downloads
|
|
100
|
+
Network: http://192.168.1.42:8080/?token=a3f9b2c1
|
|
101
|
+
Token: a3f9b2c1
|
|
102
|
+
|
|
103
|
+
Ctrl+C to stop
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Open the URL in any browser on your network and you're in. Scan the QR code to open it directly on your phone.
|
|
107
|
+
|
|
108
|
+
## QR code
|
|
109
|
+
|
|
110
|
+
The QR code is printed automatically if the `qrcode` package is installed:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pip install qrcode
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Without it, servepass works normally and just shows the URL.
|
|
117
|
+
|
|
118
|
+
## Known limitations
|
|
119
|
+
|
|
120
|
+
servepass uses a token in the URL over plain HTTP. The token is not encrypted in transit. This is fine for a trusted local network. It is not a substitute for HTTPS or VPN on public or untrusted networks.
|
|
121
|
+
|
|
122
|
+
If you're on public WiFi, use something else.
|
|
123
|
+
|
|
124
|
+
## Who this is for
|
|
125
|
+
|
|
126
|
+
* You're on a trusted LAN (home, office, workshop) and want to share files quickly
|
|
127
|
+
* You want to download files on your phone without cables or cloud storage
|
|
128
|
+
* You don't want to expose an unauthenticated HTTP server to everyone on the network
|
|
129
|
+
* You want something lightweight with zero setup time
|
|
130
|
+
|
|
131
|
+
## Comparison
|
|
132
|
+
|
|
133
|
+
| | servepass | python -m http.server | filebrowser |
|
|
134
|
+
|---|---|---|---|
|
|
135
|
+
| Authentication | Token | No | Yes |
|
|
136
|
+
| Install | `pipx install servepass` | Built-in | Binary + config |
|
|
137
|
+
| Dependencies | None | None | None |
|
|
138
|
+
| LAN IP detection | Yes | No | Yes |
|
|
139
|
+
| QR code | Optional | No | No |
|
|
140
|
+
| File upload | Planned | No | Yes |
|
|
141
|
+
|
|
142
|
+
## Roadmap
|
|
143
|
+
|
|
144
|
+
- [x] Serve with token auth
|
|
145
|
+
- [x] Auto-detect LAN IP
|
|
146
|
+
- [x] QR code in terminal (optional)
|
|
147
|
+
- [ ] `--token` flag to set a fixed token
|
|
148
|
+
- [ ] HTTPS with self-signed cert
|
|
149
|
+
- [ ] File upload via browser
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
servepass/__init__.py
|
|
5
|
+
servepass/__main__.py
|
|
6
|
+
servepass.egg-info/PKG-INFO
|
|
7
|
+
servepass.egg-info/SOURCES.txt
|
|
8
|
+
servepass.egg-info/dependency_links.txt
|
|
9
|
+
servepass.egg-info/entry_points.txt
|
|
10
|
+
servepass.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
servepass
|