Skip to content

Commit 8149653

Browse files
melevittflclaude
andcommitted
Add dassiedrop.env config file support for portable builds
Load KEY=VALUE pairs from dassiedrop.env next to the binary at startup, before any os.environ.get() calls. System env vars take precedence. Works on Windows and Linux (frozen binary or running from source). Includes dassiedrop.env.example with all user-facing options documented for both Windows and Linux path formats. The example file is bundled in CI build artifacts and release assets alongside dassiedrop.exe. A new binary test verifies that HTTP_PORT set only in dassiedrop.env (not in the process environment) causes the server to bind on that port. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f002ef2 commit 8149653

5 files changed

Lines changed: 104 additions & 2 deletions

File tree

.github/workflows/build-windows.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ jobs:
4242
uses: actions/upload-artifact@v4
4343
with:
4444
name: dassiedrop-windows-${{ steps.version.outputs.version }}
45-
path: dist/dassiedrop.exe
45+
path: |
46+
dist/dassiedrop.exe
47+
dassiedrop.env.example
4648
if-no-files-found: error
4749

4850
- name: Create release asset
4951
if: startsWith(github.ref, 'refs/tags/')
5052
uses: softprops/action-gh-release@v2
5153
with:
52-
files: dist/dassiedrop.exe
54+
files: |
55+
dist/dassiedrop.exe
56+
dassiedrop.env.example
5357
name: DassieDrop ${{ steps.version.outputs.version }}
5458
generate_release_notes: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ __pycache__/
44

55
.venv/
66
.env
7+
dassiedrop.env
78
.codex/
89

910
uploads/

dassiedrop.env.example

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# DassieDrop configuration file
2+
# Place this file as dassiedrop.env in the same folder as the dassiedrop binary.
3+
# Environment variables set before launching the binary always take precedence.
4+
# Format: KEY=VALUE (no spaces required around =)
5+
# Comments start with #. A # inside a value is part of the value.
6+
# Quotes are optional: HOST="0.0.0.0" and HOST=0.0.0.0 are equivalent.
7+
8+
# --- Network ---
9+
# HTTP_PORT=8000
10+
# HOST=0.0.0.0
11+
12+
# --- Storage ---
13+
# Directory where uploaded files are stored.
14+
# Windows:
15+
# UPLOAD_DIR=C:\DassieDrop\uploads
16+
# Linux/macOS:
17+
# UPLOAD_DIR=/opt/dassiedrop/uploads
18+
# Maximum total storage in bytes. 0 = unlimited.
19+
# MAX_TOTAL_STORAGE_BYTES=0
20+
21+
# --- Access Control ---
22+
# App-level password. Anyone opening the app must enter this. Empty = open access.
23+
# ACCESS_CODE=
24+
# Programmatic API key (X-API-Key header). Empty = disabled.
25+
# API_KEY=
26+
# Admin password that can access any workspace.
27+
# WORKSPACE_SUPER_PASSWORD=
28+
29+
# --- Sessions ---
30+
# SESSION_TTL_SECONDS=604800
31+
32+
# --- HTTPS ---
33+
# Set to 1 to enable HTTPS. A self-signed certificate is generated automatically.
34+
# HTTPS=0
35+
# HTTPS_PORT=8443
36+
# Hostname for the self-signed certificate. Set to your machine's hostname or LAN IP.
37+
# HTTPS_SELF_SIGNED_HOST=localhost
38+
39+
# --- Share Links ---
40+
# Base URL prepended to share links. Useful behind a reverse proxy.
41+
# SHARE_BASE_URL=
42+
43+
# --- Auto-update check ---
44+
# UPDATE_CHECK_ENABLED=0

dassiedrop/config.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@
2525
VERSION_FILE = BASE_DIR / "VERSION"
2626
_WRITABLE_BASE = BASE_DIR
2727

28+
29+
def _load_env_file(path: Path) -> None:
30+
try:
31+
text = path.read_text(encoding="utf-8-sig")
32+
except OSError:
33+
return
34+
for line in text.splitlines():
35+
line = line.strip()
36+
if not line or line.startswith("#"):
37+
continue
38+
if "=" not in line:
39+
continue
40+
key, _, value = line.partition("=")
41+
key = key.strip()
42+
if not key:
43+
continue
44+
value = value.strip()
45+
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
46+
value = value[1:-1]
47+
if key not in os.environ:
48+
os.environ[key] = value
49+
50+
51+
_load_env_file(_WRITABLE_BASE / "dassiedrop.env")
52+
2853
UPLOAD_DIR = Path(os.environ.get("UPLOAD_DIR", str(_WRITABLE_BASE / "uploads"))).resolve()
2954
MAX_FILE_SIZE = 1024 * 1024 * 1024 # 1 GB
3055
MAX_JSON_BODY_SIZE = int(os.environ.get("MAX_JSON_BODY_SIZE", str(1024 * 1024)))

tests/test_binary.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,34 @@ def test_workspace_index_is_written_to_upload_dir(self) -> None:
372372
payload = json.loads(index_file.read_text(encoding="utf-8"))
373373
self.assertIn("workspaces", payload)
374374

375+
def test_env_file_is_loaded(self) -> None:
376+
port = _find_free_port()
377+
env_file = BINARY.parent / "dassiedrop.env"
378+
env_file.write_text(f"HTTP_PORT={port}\n", encoding="utf-8")
379+
try:
380+
env = {
381+
**os.environ,
382+
"HOST": "127.0.0.1",
383+
"UPLOAD_DIR": str(self._upload_dir),
384+
"HTTPS": "",
385+
}
386+
env.pop("HTTP_PORT", None)
387+
env.pop("PORT", None)
388+
proc = subprocess.Popen(
389+
[str(BINARY)],
390+
env=env,
391+
stdout=subprocess.DEVNULL,
392+
stderr=subprocess.DEVNULL,
393+
)
394+
self._procs.append(proc)
395+
self.assertTrue(
396+
_wait_for_server(port),
397+
f"Binary did not bind on port {port} from dassiedrop.env — "
398+
"env file may not be loaded",
399+
)
400+
finally:
401+
env_file.unlink(missing_ok=True)
402+
375403

376404
if __name__ == "__main__":
377405
unittest.main()

0 commit comments

Comments
 (0)