vaalboks 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.
Files changed (35) hide show
  1. vaalboks-0.1.0/LICENSE +21 -0
  2. vaalboks-0.1.0/PKG-INFO +140 -0
  3. vaalboks-0.1.0/README.md +115 -0
  4. vaalboks-0.1.0/pyproject.toml +65 -0
  5. vaalboks-0.1.0/setup.cfg +4 -0
  6. vaalboks-0.1.0/share/__init__.py +0 -0
  7. vaalboks-0.1.0/share/admin.py +1 -0
  8. vaalboks-0.1.0/share/apps.py +5 -0
  9. vaalboks-0.1.0/share/middleware.py +37 -0
  10. vaalboks-0.1.0/share/migrations/__init__.py +0 -0
  11. vaalboks-0.1.0/share/models.py +1 -0
  12. vaalboks-0.1.0/share/static/share/app.css +103 -0
  13. vaalboks-0.1.0/share/static/share/app.js +233 -0
  14. vaalboks-0.1.0/share/static/share/favicon.ico +0 -0
  15. vaalboks-0.1.0/share/static/share/favicon.svg +13 -0
  16. vaalboks-0.1.0/share/static/share/htmx.min.js +1873 -0
  17. vaalboks-0.1.0/share/static/share/logo.svg +20 -0
  18. vaalboks-0.1.0/share/templates/share/_file_list.html +27 -0
  19. vaalboks-0.1.0/share/templates/share/index.html +44 -0
  20. vaalboks-0.1.0/share/tests.py +44 -0
  21. vaalboks-0.1.0/share/urls.py +12 -0
  22. vaalboks-0.1.0/share/views.py +128 -0
  23. vaalboks-0.1.0/vaalboks/__init__.py +0 -0
  24. vaalboks-0.1.0/vaalboks/asgi.py +16 -0
  25. vaalboks-0.1.0/vaalboks/cli.py +150 -0
  26. vaalboks-0.1.0/vaalboks/gunicorn.py +31 -0
  27. vaalboks-0.1.0/vaalboks/settings.py +158 -0
  28. vaalboks-0.1.0/vaalboks/urls.py +24 -0
  29. vaalboks-0.1.0/vaalboks/wsgi.py +16 -0
  30. vaalboks-0.1.0/vaalboks.egg-info/PKG-INFO +140 -0
  31. vaalboks-0.1.0/vaalboks.egg-info/SOURCES.txt +33 -0
  32. vaalboks-0.1.0/vaalboks.egg-info/dependency_links.txt +1 -0
  33. vaalboks-0.1.0/vaalboks.egg-info/entry_points.txt +2 -0
  34. vaalboks-0.1.0/vaalboks.egg-info/requires.txt +7 -0
  35. vaalboks-0.1.0/vaalboks.egg-info/top_level.txt +2 -0
vaalboks-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vaalboks contributors
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.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: vaalboks
3
+ Version: 0.1.0
4
+ Summary: Simple local-network file sharing over HTTPS
5
+ Author: vaalboks contributors
6
+ License-Expression: MIT
7
+ Keywords: django,file-sharing,lan,self-hosted
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Framework :: Django
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
14
+ Requires-Python: >=3.14
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: cryptography>=46.0.0
18
+ Requires-Dist: django>=6.1.1
19
+ Requires-Dist: gunicorn>=26.2.0
20
+ Requires-Dist: uvicorn>=0.52.4
21
+ Requires-Dist: uvicorn-worker>=0.4.0
22
+ Requires-Dist: whitenoise>=6.12.0
23
+ Requires-Dist: zstandard>=0.25.0
24
+ Dynamic: license-file
25
+
26
+ # vaalboks
27
+
28
+ <p align="center">
29
+ <img src="share/static/share/logo.svg" alt="vaalboks logo" width="520">
30
+ </p>
31
+
32
+ Vaalboks is a small file-sharing server for a local network. Drop files or
33
+ folders in the browser, then download them from another device on the same
34
+ network.
35
+
36
+ The name is Afrikaans for “dull box” — a straightforward tool for moving files
37
+ between computers without a separate hosted service.
38
+
39
+ ## Stack
40
+
41
+ - Python 3.14, Django 6.1, uv
42
+ - Frontend: plain HTML/CSS/JS + htmx 4.0
43
+ - Uploads use XHR for live progress and speed tracking; downloads use streaming
44
+ `fetch`
45
+
46
+ ## Run
47
+
48
+ After installing the package, start the server with:
49
+
50
+ ```sh
51
+ uvx vaalboks
52
+ ```
53
+
54
+ This starts HTTPS on `0.0.0.0:8443` with two workers. Runtime data is stored
55
+ in `./vaalboks-data`, and a self-signed certificate is generated there on
56
+ first launch. Migrations and static-file collection run automatically. Use
57
+ `--http` for plain HTTP on port 8123. The CLI uses Gunicorn on Linux and macOS
58
+ and Uvicorn on Windows.
59
+
60
+ When running from this checkout:
61
+
62
+ ```sh
63
+ uv run python manage.py runserver 0.0.0.0:8123
64
+ ```
65
+
66
+ Then open `http://<your-LAN-IP>:8123/` from any device on the network.
67
+ (Find your IP with `ipconfig getifaddr en0`.)
68
+
69
+ ## Use as a Django app
70
+
71
+ The file-sharing interface can also be mounted in an existing Django project.
72
+ Install `vaalboks`, add `share` to `INSTALLED_APPS`, configure the directory
73
+ used for uploads, and include the app URLs:
74
+
75
+ ```python
76
+ # settings.py
77
+ INSTALLED_APPS = [
78
+ # ...
79
+ "share",
80
+ ]
81
+
82
+ VAALBOKS_SHARED_ROOT = BASE_DIR / "shared"
83
+ ```
84
+
85
+ ```python
86
+ # urls.py
87
+ from django.urls import include, path
88
+
89
+ urlpatterns = [
90
+ path("share/", include("share.urls")),
91
+ ]
92
+ ```
93
+
94
+ The app does not require the bundled `vaalboks` settings, middleware, or
95
+ server. The host project remains responsible for Django middleware, static
96
+ files, CSRF, and deployment configuration.
97
+
98
+ ## Direct Gunicorn HTTPS + zstd
99
+
100
+ This section applies to Linux and macOS. Windows users should use the `vaalboks`
101
+ command, which selects Uvicorn automatically.
102
+
103
+ Gunicorn can terminate HTTPS, and Django compresses eligible text responses
104
+ with zstd:
105
+
106
+ ```sh
107
+ uv run gunicorn vaalboks.asgi:application \
108
+ --worker-class uvicorn_worker.UvicornWorker \
109
+ --workers 2 \
110
+ --bind 0.0.0.0:8443 \
111
+ --certfile certs/vaalboks-cert.pem \
112
+ --keyfile certs/vaalboks-key.pem
113
+ ```
114
+
115
+ Open `https://<your-LAN-IP>:8443/` and trust the self-signed certificate on
116
+ each device that will connect. This setup provides HTTPS and zstd compression.
117
+
118
+ The CLI also accepts `--host`, `--port`, `--workers`, `--data-dir`,
119
+ `--certfile`, and `--keyfile`.
120
+
121
+ ## Publishing
122
+
123
+ Build artifacts locally with:
124
+
125
+ ```sh
126
+ uv build
127
+ ```
128
+
129
+ The repository includes GitHub Actions for quality checks and publishing on a
130
+ published GitHub release. Configure PyPI trusted publishing for the GitHub
131
+ repository before creating the first release; no long-lived PyPI token is
132
+ required.
133
+
134
+ ## How it works
135
+
136
+ - `GET /` — single page with drop zone and live file listing
137
+ - `GET /api/files/` — htmx partial that re-renders the listing after uploads
138
+ - `POST /api/upload/` — multipart upload; folders are traversed client-side
139
+ (`webkitGetAsEntry`) and each file is sent with its relative path
140
+ - `GET /files/<path>` — download a shared file (path-traversal protected)
@@ -0,0 +1,115 @@
1
+ # vaalboks
2
+
3
+ <p align="center">
4
+ <img src="share/static/share/logo.svg" alt="vaalboks logo" width="520">
5
+ </p>
6
+
7
+ Vaalboks is a small file-sharing server for a local network. Drop files or
8
+ folders in the browser, then download them from another device on the same
9
+ network.
10
+
11
+ The name is Afrikaans for “dull box” — a straightforward tool for moving files
12
+ between computers without a separate hosted service.
13
+
14
+ ## Stack
15
+
16
+ - Python 3.14, Django 6.1, uv
17
+ - Frontend: plain HTML/CSS/JS + htmx 4.0
18
+ - Uploads use XHR for live progress and speed tracking; downloads use streaming
19
+ `fetch`
20
+
21
+ ## Run
22
+
23
+ After installing the package, start the server with:
24
+
25
+ ```sh
26
+ uvx vaalboks
27
+ ```
28
+
29
+ This starts HTTPS on `0.0.0.0:8443` with two workers. Runtime data is stored
30
+ in `./vaalboks-data`, and a self-signed certificate is generated there on
31
+ first launch. Migrations and static-file collection run automatically. Use
32
+ `--http` for plain HTTP on port 8123. The CLI uses Gunicorn on Linux and macOS
33
+ and Uvicorn on Windows.
34
+
35
+ When running from this checkout:
36
+
37
+ ```sh
38
+ uv run python manage.py runserver 0.0.0.0:8123
39
+ ```
40
+
41
+ Then open `http://<your-LAN-IP>:8123/` from any device on the network.
42
+ (Find your IP with `ipconfig getifaddr en0`.)
43
+
44
+ ## Use as a Django app
45
+
46
+ The file-sharing interface can also be mounted in an existing Django project.
47
+ Install `vaalboks`, add `share` to `INSTALLED_APPS`, configure the directory
48
+ used for uploads, and include the app URLs:
49
+
50
+ ```python
51
+ # settings.py
52
+ INSTALLED_APPS = [
53
+ # ...
54
+ "share",
55
+ ]
56
+
57
+ VAALBOKS_SHARED_ROOT = BASE_DIR / "shared"
58
+ ```
59
+
60
+ ```python
61
+ # urls.py
62
+ from django.urls import include, path
63
+
64
+ urlpatterns = [
65
+ path("share/", include("share.urls")),
66
+ ]
67
+ ```
68
+
69
+ The app does not require the bundled `vaalboks` settings, middleware, or
70
+ server. The host project remains responsible for Django middleware, static
71
+ files, CSRF, and deployment configuration.
72
+
73
+ ## Direct Gunicorn HTTPS + zstd
74
+
75
+ This section applies to Linux and macOS. Windows users should use the `vaalboks`
76
+ command, which selects Uvicorn automatically.
77
+
78
+ Gunicorn can terminate HTTPS, and Django compresses eligible text responses
79
+ with zstd:
80
+
81
+ ```sh
82
+ uv run gunicorn vaalboks.asgi:application \
83
+ --worker-class uvicorn_worker.UvicornWorker \
84
+ --workers 2 \
85
+ --bind 0.0.0.0:8443 \
86
+ --certfile certs/vaalboks-cert.pem \
87
+ --keyfile certs/vaalboks-key.pem
88
+ ```
89
+
90
+ Open `https://<your-LAN-IP>:8443/` and trust the self-signed certificate on
91
+ each device that will connect. This setup provides HTTPS and zstd compression.
92
+
93
+ The CLI also accepts `--host`, `--port`, `--workers`, `--data-dir`,
94
+ `--certfile`, and `--keyfile`.
95
+
96
+ ## Publishing
97
+
98
+ Build artifacts locally with:
99
+
100
+ ```sh
101
+ uv build
102
+ ```
103
+
104
+ The repository includes GitHub Actions for quality checks and publishing on a
105
+ published GitHub release. Configure PyPI trusted publishing for the GitHub
106
+ repository before creating the first release; no long-lived PyPI token is
107
+ required.
108
+
109
+ ## How it works
110
+
111
+ - `GET /` — single page with drop zone and live file listing
112
+ - `GET /api/files/` — htmx partial that re-renders the listing after uploads
113
+ - `POST /api/upload/` — multipart upload; folders are traversed client-side
114
+ (`webkitGetAsEntry`) and each file is sent with its relative path
115
+ - `GET /files/<path>` — download a shared file (path-traversal protected)
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "vaalboks"
3
+ version = "0.1.0"
4
+ description = "Simple local-network file sharing over HTTPS"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ authors = [{ name = "vaalboks contributors" }]
10
+ keywords = ["django", "file-sharing", "lan", "self-hosted"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Framework :: Django",
15
+ "Programming Language :: Python :: 3 :: Only",
16
+ "Programming Language :: Python :: 3.14",
17
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
18
+ ]
19
+ dependencies = [
20
+ "cryptography>=46.0.0",
21
+ "django>=6.1.1",
22
+ "gunicorn>=26.2.0",
23
+ "uvicorn>=0.52.4",
24
+ "uvicorn-worker>=0.4.0",
25
+ "whitenoise>=6.12.0",
26
+ "zstandard>=0.25.0",
27
+ ]
28
+
29
+ [project.scripts]
30
+ vaalboks = "vaalboks.cli:main"
31
+
32
+ [build-system]
33
+ requires = ["setuptools>=80"]
34
+ build-backend = "setuptools.build_meta"
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["vaalboks", "vaalboks.*", "share", "share.*"]
38
+
39
+ [tool.setuptools.package-data]
40
+ share = ["templates/**/*.html", "static/**/*"]
41
+
42
+ [tool.ruff]
43
+ target-version = "py314"
44
+ line-length = 100
45
+ extend-exclude = ["shared", "vaalboks-data", "build", "certs"]
46
+
47
+ #[tool.ruff.lint]
48
+ #select = ["E", "F", "I", "B", "UP", "RUF"]
49
+
50
+ [tool.ruff.format]
51
+ quote-style = "double"
52
+ indent-style = "space"
53
+
54
+ [tool.ty.environment]
55
+ python-version = "3.14"
56
+
57
+ [tool.ty.src]
58
+ include = ["manage.py", "vaalboks", "share"]
59
+ exclude = ["shared/**", "vaalboks-data/**", "build/**"]
60
+
61
+ [dependency-groups]
62
+ dev = [
63
+ "ruff>=0.16.6",
64
+ "ty>=0.0.78",
65
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1 @@
1
+ # Register your models here.
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class ShareConfig(AppConfig):
5
+ name = "share"
@@ -0,0 +1,37 @@
1
+ import zstandard
2
+
3
+
4
+ class ZstdMiddleware:
5
+ """Compress eligible non-streaming responses when the client supports zstd."""
6
+
7
+ min_size = 1024
8
+
9
+ def __init__(self, get_response):
10
+ self.get_response = get_response
11
+ self.compressor = zstandard.ZstdCompressor(level=3)
12
+
13
+ def __call__(self, request):
14
+ response = self.get_response(request)
15
+ accepts_zstd = "zstd" in request.headers.get("Accept-Encoding", "").lower()
16
+ content_type = response.headers.get("Content-Type", "").lower()
17
+
18
+ if (
19
+ accepts_zstd
20
+ and not response.streaming
21
+ and response.status_code not in (204, 304)
22
+ and "Content-Encoding" not in response.headers
23
+ and (
24
+ content_type.startswith("text/")
25
+ or "javascript" in content_type
26
+ or "json" in content_type
27
+ or "xml" in content_type
28
+ )
29
+ ):
30
+ body = b"".join(response)
31
+ if len(body) >= self.min_size:
32
+ response.content = self.compressor.compress(body)
33
+ response.headers["Content-Encoding"] = "zstd"
34
+ response.headers.pop("Content-Length", None)
35
+ response.headers["Vary"] = "Accept-Encoding"
36
+
37
+ return response
File without changes
@@ -0,0 +1 @@
1
+ # Create your models here.
@@ -0,0 +1,103 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --bg: light-dark(#f6f7f9, #14161a);
4
+ --panel: light-dark(#ffffff, #1d2026);
5
+ --border: light-dark(#e2e5ea, #30343d);
6
+ --accent: #4f7cff;
7
+ --text: light-dark(#1b1e24, #e8eaee);
8
+ --muted: light-dark(#6b7280, #9aa1ad);
9
+ }
10
+ * { box-sizing: border-box; }
11
+ body {
12
+ margin: 0;
13
+ font-family: system-ui, -apple-system, sans-serif;
14
+ background: var(--bg);
15
+ color: var(--text);
16
+ display: flex;
17
+ justify-content: center;
18
+ padding: 2rem 1rem;
19
+ }
20
+ main { width: min(720px, 100%); }
21
+ .brand-logo {
22
+ display: block;
23
+ width: min(100%, 360px);
24
+ height: auto;
25
+ margin: 0 auto 1.25rem;
26
+ }
27
+ h1 { font-size: 1.05rem; margin: 0 0 1.25rem; }
28
+ h1 .subtitle { color: var(--muted); font-weight: 400; }
29
+ #dropzone {
30
+ border: 2px dashed var(--border);
31
+ border-radius: 12px;
32
+ background: var(--panel);
33
+ padding: 3rem 1.5rem;
34
+ text-align: center;
35
+ color: var(--muted);
36
+ transition: border-color .15s, background .15s;
37
+ cursor: pointer;
38
+ }
39
+ #dropzone.dragover {
40
+ border-color: var(--accent);
41
+ background: color-mix(in srgb, var(--accent) 8%, var(--panel));
42
+ color: var(--text);
43
+ }
44
+ #dropzone p { margin: .35rem 0; }
45
+ #dropzone .big { font-size: 1.05rem; color: var(--text); }
46
+ #progress-wrap { margin-top: 1rem; display: none; }
47
+ #progress-wrap.active { display: block; }
48
+ progress { width: 100%; height: 8px; }
49
+ #progress-label { font-size: .85rem; color: var(--muted); margin-top: .3rem; }
50
+ #stats {
51
+ margin-top: 1rem;
52
+ display: flex;
53
+ gap: 1.25rem;
54
+ font-size: .82rem;
55
+ color: var(--muted);
56
+ font-variant-numeric: tabular-nums;
57
+ }
58
+ #stats b { color: var(--text); font-weight: 600; }
59
+ #file-list {
60
+ margin-top: 1.5rem;
61
+ background: var(--panel);
62
+ border: 1px solid var(--border);
63
+ border-radius: 12px;
64
+ overflow: hidden;
65
+ }
66
+ #file-list:empty::after {
67
+ content: "Nothing shared yet — drop some files above.";
68
+ display: block;
69
+ padding: 1.5rem;
70
+ color: var(--muted);
71
+ text-align: center;
72
+ }
73
+ .row {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: .6rem;
77
+ padding: .55rem 1rem;
78
+ border-top: 1px solid var(--border);
79
+ font-size: .92rem;
80
+ }
81
+ .row:first-child { border-top: none; }
82
+ .folder-toggle {
83
+ width: 1.1em;
84
+ padding: 0;
85
+ border: 0;
86
+ background: none;
87
+ color: var(--muted);
88
+ font: inherit;
89
+ font-size: 1.3rem;
90
+ line-height: 1;
91
+ cursor: pointer;
92
+ transition: transform .15s;
93
+ }
94
+ .folder-toggle[aria-expanded="true"] { transform: rotate(90deg); }
95
+ .folder-toggle.empty { visibility: hidden; cursor: default; }
96
+ .children { display: none; padding-left: 1.4rem; }
97
+ .children.open { display: block; }
98
+ .row .icon { width: 1.1em; text-align: center; }
99
+ .row .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
100
+ .row .size { margin-left: auto; color: var(--muted); font-variant-numeric: tabular-nums; }
101
+ .row a { color: var(--accent); text-decoration: none; }
102
+ .row a:hover { text-decoration: underline; }
103
+ .row.dir { color: var(--muted); }