Configuration¶
unit3dprep reads configuration from three layers, in priority order:
- Environment variables — always win.
- Shared
.envfile — single file read by bothunit3dprepand theUnit3DWebUp0.0.20+ bot. Editable from the Web UI through Settings. - Built-in defaults — used when neither env nor file sets a value.
U3DP_* and W_* keys are re-read on every access via config.runtime_setting(): changing them from the Web UI takes effect immediately, no restart. Exceptions are U3DP_HOST, U3DP_PORT, U3DP_ROOT_PATH, and U3DP_HTTPS_ONLY, which are read once at uvicorn startup.
Environment variables¶
Required by the Web UI¶
| Variable | Description |
|---|---|
U3DP_PASSWORD_HASH |
Bcrypt hash of the web password. Generated by generate_hash.py. Always single-quoted in .env/bash files (see Installation). |
U3DP_SECRET |
Hex secret signing session cookies. Generated by generate_hash.py. |
TMDB¶
| Variable | Default | Description |
|---|---|---|
TMDB_API_KEY |
— | TMDB v3 API key. Used by unit3dprep's TMDB lookup and metadata fetch. The CLI prompts the user if missing. The same value must also exist as TMDB_APIKEY in the shared .env (Unit3DWebUp requires it). |
Runtime (re-read on every access)¶
| Variable | Default | Description |
|---|---|---|
U3DP_HOST |
127.0.0.1 |
uvicorn bind address. (read-once at startup) |
U3DP_PORT |
8765 |
Listening port. On Ultra.cc pick a reserved port (app-ports free). (read-once) |
U3DP_ROOT_PATH |
"" |
Nginx prefix (e.g. /unit3dprep). (read-once) See ROOT_PATH notes. |
U3DP_HTTPS_ONLY |
0 |
1 sets the https_only flag on the session cookie (required behind HTTPS). (read-once) |
U3DP_MEDIA_ROOT |
~/media |
Base media folder. Subfolders become categories. |
U3DP_SEEDINGS_DIR |
~/seedings |
Hardlink destination. Must live on the same FS as U3DP_MEDIA_ROOT. |
U3DP_TMDB_LANG |
it-IT |
TMDB response language (internal lookup). Examples: en-US, es-ES. |
U3DP_LANG |
it |
UI language (it/en). Persisted in localStorage + .env. |
U3DP_DB_PATH |
~/.unit3dprep_db.json |
Upload history (JSON). |
U3DP_TMDB_CACHE_PATH |
~/.unit3dprep_tmdb_cache.json |
TMDB query cache. |
U3DP_LANG_CACHE_PATH |
~/.unit3dprep_lang_cache.json |
Audio language detection cache. |
U3DP_SYSTEMD_UNIT |
unit3dprep-web.service |
App systemd user unit name, used by the "Update app" button for systemctl --user cat/restart. Override only if your unit has a different name. |
U3DP_DRY_RUN_TRACKER |
0 |
When 1 (or true/yes) the wizard skips the /upload call to Unit3DWebUp: useful in dev/WSL to exercise the pipeline (setenv→scan→maketorrent→seed) without polluting the live tracker. |
U3DP_GITHUB_REPO |
davidesidoti/unit3dprep |
owner/repo slug used to poll the app's GitHub releases (env-only, read at import). |
Unit3DWebUp bridge¶
| Variable | Default | Description |
|---|---|---|
WEBUP_URL |
http://127.0.0.1:8000 |
Base address of the Unit3DWebUp bot. |
WEBUP_REPO_PATH |
~/dev/Unit3DWebUp |
Path to the Unit3DWebUp clone (legacy). Only used by the "git" mode of the webup auto-update. |
WEBUP_VENV_BIN |
auto | bin folder of the venv that holds Unit3DwebUp. Auto-detect: if unset, try <WEBUP_REPO_PATH>/.venv/bin/python, fall back to sys.executable (canonical PyPI install in the same venv as unit3dprep). Set it explicitly when webup lives in a separate venv. |
WEBUP_SYSTEMD_UNIT |
unit3dwebup.service |
Unit3DWebUp systemd user unit, restarted after SSE auto-update. |
Shared .env storage¶
| Variable | Default | Description |
|---|---|---|
U3DP_ENV_PATH |
— | Full file path of the shared .env. Explicit unit3dprep override. |
ENVPATH |
— | Directory containing the .env (file = <dir>/.env). Same convention used by Unit3DWebUp: pass the same value to the bot's uvicorn. |
UNIT3DUP_CONFIG |
~/Unit3Dup_config/Unit3Dbot.json |
Path of the legacy Unit3Dbot.json to read during the one-shot migration at first startup. Not used afterwards. |
Precedence for the on-disk .env file: U3DP_ENV_PATH → ENVPATH/.env → ~/.config/unit3dprep/.env (XDG default).
Web UI wizard (W_*)¶
Control default state of upload wizard toggles. Stored in the shared .env, edit from Settings.
| Key | Default | Description |
|---|---|---|
W_AUDIO_CHECK |
true |
Enable Italian audio check inside the wizard. |
W_AUTO_TMDB |
true |
Auto-fetch TMDB metadata if an ID is already present. |
W_HIDE_UPLOADED |
true |
Hide already-uploaded items from the Library. |
W_HIDE_NO_ITALIAN |
false |
Hide already-scanned items without Italian audio from the Library. |
W_HARDLINK_ONLY |
false |
End the wizard after the hardlink, without invoking the Unit3DWebUp upload. |
W_CONFIRM_NAMES |
true |
Require confirmation of the final name before hardlinking. |
W_DUPLICATE_CHECK |
true |
Before hardlinking, query the ITT API (/api/torrents/filter?tmdbId=<id>) and prompt for confirmation if a torrent with the exact same byte size already exists. See Usage › Wizard › Duplicate check. |
Shared .env file¶
Single storage for app + bot, conforming to Unit3DWebUp 0.0.20+. Default path: ~/.config/unit3dprep/.env (override via U3DP_ENV_PATH or ENVPATH).
Keys understood by webup are written on disk with the canonical naming the bot expects (prefixes TRACKER__, TORRENT__, PREFS__); in memory and in the /api/settings API the short historical names (ITT_APIKEY, QBIT_HOST, …) are kept. Translation is confined to unit3dprep/web/config.py (WEBUP_KEY_MAP).
To run the bot alongside, just point it at the same ENVPATH:
Short ↔ canonical mapping (extract)¶
| Short name (API/UI) | Canonical name (on disk / .env) |
|---|---|
ITT_APIKEY |
TRACKER__ITT_APIKEY |
ITT_URL |
TRACKER__ITT_URL |
ITT_PID |
TRACKER__ITT_PID |
MULTI_TRACKER |
TRACKER__MULTI_TRACKER=["itt", ...] (JSON array) |
TMDB_APIKEY |
TRACKER__TMDB_APIKEY |
TVDB_APIKEY |
TRACKER__TVDB_APIKEY |
QBIT_HOST / QBIT_PORT / QBIT_USER / QBIT_PASS |
TORRENT__QBIT_HOST / …_PORT / …_USER / …_PASS |
IMGFI_KEY |
TRACKER__IMGFI_KEY |
IMAGE_HOST_ORDER |
projected to PREFS__<HOST>_PRIORITY (1, 2, …, 99 for hosts not in the list) |
TAG_ORDER_MOVIE / TAG_ORDER_SERIE |
PREFS__TAG_POSITION_MOVIE / …_SERIE |
NUMBER_OF_SCREENSHOTS, ANON, PERSONAL_RELEASE, … |
PREFS__<KEY> |
The full list lives in unit3dprep/web/config.py (WEBUP_KEY_MAP).
PREFERRED_LANG — ISO 639-1 format¶
PREFS__PREFERRED_LANG (short name PREFERRED_LANG) must be a 2-letter ISO 639-1 code: "it", "en", "fr", "es", etc. Webup 0.0.25 compares this value against each audio track's language field in the mediainfo, which mediainfo emits as the 2-letter code. Using ISO 639-2 ("ita", "eng") the match silently fails, media.can_upload becomes False, and UploadUseCase.execute() skips the tracker submission without emitting any WebSocket log — qBit seeds locally but the tracker responds "InfoHash not found" on announce.
Default in DEFAULT_CONFIG: "it" (since v0.6.4+). Editable from Settings → Behavior → Preferred language. See Troubleshooting › silent /upload.
Skip rules¶
_to_webup_env_payload skips empty values ("", None) and the placeholders no_key / no_pass / no_path / no_comment from the push to Unit3DWebUp: the bot keeps its own defaults and its pydantic validator (empty_to_none) does not hit None-to-str conversion errors that would SystemExit(1) on the next setenv.
Three keys are exceptions: PREFS__TORRENT_ARCHIVE_PATH, PREFS__WATCHER_PATH, PREFS__WATCHER_DESTINATION_PATH, PREFS__SCAN_PATH are materialized with a . fallback when our config has them empty, because webup requires a "Path-able" value that exists (otherwise get_settings() calls SystemExit).
Automatic migration from legacy Unit3Dbot.json¶
On the first startup, if ~/Unit3Dup_config/Unit3Dbot.json (or whatever $UNIT3DUP_CONFIG points to) exists:
- the file is read;
- rewritten as
.envwith the canonical naming; - renamed to
Unit3Dbot.json.migrated-bak.
The migration is idempotent. The backup is never deleted by the app — the user can remove it manually after confirming everything works.
Secret masking¶
Secrets (API keys, passwords, PIDs) are masked as "__SET__" in GET /api/settings responses. On PUT, if the client sends back "__SET__" the server preserves the original value. This avoids leaks in browser logs while letting you edit other keys without re-entering every secret.
Masked keys are defined in unit3dprep/web/config.py (MASKED_KEYS):
ITT_APIKEY, ITT_PID, PTT_APIKEY, PTT_PID, SIS_APIKEY, SIS_PID, TMDB_APIKEY, TVDB_APIKEY, YOUTUBE_KEY, IGDB_CLIENT_ID, IGDB_ID_SECRET, QBIT_PASS, TRASM_PASS, RTORR_PASS, FTPX_PASS, PTSCREENS_KEY, PASSIMA_KEY, IMGBB_KEY, IMGFI_KEY, FREE_IMAGE_KEY, LENSDUMP_KEY, IMARIDE_KEY.
Atomic writes + live sync¶
Writes to the .env go through tempfile.mkstemp + os.replace → neither unit3dprep nor Unit3DWebUp ever sees a half-written file.
Every PUT /api/settings cascades into webup_client.setenv(key, value) (POST /setenv to the bot) for each canonical key changed: the bot rebuilds Settings() internally without restart. A Save in the UI does not require systemctl restart unit3dwebup.service.
Exceptions that need a bot restart: REDIS_* (anyway hardcoded to localhost:6379), changes to process env-vars not in .env (e.g. DOCKER, PYTHONUNBUFFERED), and obviously upgrading the webup package (handled automatically by the SSE auto-update).
U3DP_ROOT_PATH and nginx¶
When you serve the Web UI behind a reverse proxy at a subpath (e.g. /unit3dprep), set:
Important — on Ultra.cc the nginx user-proxy does not strip the prefix: requests arrive at uvicorn with /unit3dprep still in place. That is why the app mounts routes under the prefix with app.include_router(r, prefix=ROOT_PATH) instead of FastAPI(root_path=...) (the latter is only for proxies that do strip).
The SPA reads window.__ROOT_PATH__ injected at serve-time into index.html, so assets and API calls automatically hit the right prefix with no rebuild.
On a generic VPS where nginx strips the prefix (proxy_pass http://127.0.0.1:8765/; with trailing slash), set U3DP_ROOT_PATH="" instead and handle the path from the server block.
In-app auto-update¶
The Web UI ships an integrated update system for two distinct artifacts:
- App (
unit3dprep) — installed version (importlib.metadata.version("unit3dprep")orpyproject.tomlin git mode) compared against GitHub Releases. - Unit3DWebUp — version installed in the bot's venv (detected via subprocess
python -c "import importlib.metadata; print(importlib.metadata.version('Unit3DwebUp'))") compared against PyPI.
Cards rendered in Settings › Version. Click "Install update":
- Modal with
pip install --upgrade Unit3DwebUporgit pull + pip install -e .live-streamed via SSE (/api/version/update/{webup,app}/stream). - Once done,
/api/version/info_cacheis cleared and systemd<unit>is restarted in a transient scope (systemd-run --user --on-active=3s) to survive the parent process SIGTERM. - 5s countdown + browser reload; release-body popup post-reload.
App prerequisites¶
The "Update app" button stays disabled (can_update_app: false) when:
systemctlis not inPATH, or- the configured unit does not exist (
systemctl --user cat <unit>fails).
The default for U3DP_SYSTEMD_UNIT is already unit3dprep-web.service (the name used on both Ultra.cc and the VPS deploy). If your unit has a different name, set it:
# ~/.config/systemd/user/unit3dprep-web.service
[Service]
Environment=U3DP_SYSTEMD_UNIT=unit3dprep-web.service
or save the key through Settings › App Auto-Update. The key is read at runtime, so a Save takes effect immediately. daemon-reload + restart are only needed when you add a fresh Environment= entry to the unit file.
Webup prerequisites¶
Likewise, the "Update" button on the webup card requires:
- a python interpreter with
Unit3DwebUpinstalled accessible. Auto-detect:WEBUP_VENV_BIN/pythonif set, then<WEBUP_REPO_PATH>/.venv/bin/python(legacy), finallysys.executable(canonical PyPI install in the same venv as unit3dprep). SetWEBUP_VENV_BINexplicitly only when webup lives in a separate venv; - the
unit3dwebup.servicesystemd user unit existing (override withWEBUP_SYSTEMD_UNIT).
Install mode (app)¶
_install_mode() picks the flow based on the install layout:
- git — Python source has reachable
.git(typical forpip install -e .from a checkout). Runsgit pull --ff-only origin main+pip install -e .. - pip — installed via
pip install git+https://...@vX(no.git). Runspip install --upgrade --force-reinstall git+URL@vX.
Editable install + service WorkingDirectory
If the systemd [Service] has WorkingDirectory=<repo> and that folder contains .git, Python imports unit3dprep from the local source (editable) even when you installed via pip install git+.... The flow becomes "git" as a consequence. To force the pip flow, use WorkingDirectory=%h and remove/rename the checkout.
Complete examples¶
Local deploy (just you, on a trusted machine)¶
export U3DP_PASSWORD_HASH='$2b$12$...'
export U3DP_SECRET="..."
export TMDB_API_KEY="..."
export U3DP_HOST="127.0.0.1"
export U3DP_PORT="8765"
export ENVPATH="$HOME/.config/unit3dprep"
# bot in background
ENVPATH=$HOME/.config/unit3dprep uvicorn unit3dwup.start:app --host 127.0.0.1 --port 8000 &
unit3dprep-web
Behind nginx with HTTPS and a subpath¶
export U3DP_PASSWORD_HASH='$2b$12$...'
export U3DP_SECRET="..."
export TMDB_API_KEY="..."
export U3DP_HOST="127.0.0.1"
export U3DP_PORT="45678" # backend port, not exposed
export U3DP_ROOT_PATH="/unit3dprep" # public subpath
export U3DP_HTTPS_ONLY="1"
export ENVPATH="$HOME/.config/unit3dprep"
unit3dprep-web
Media on a separate disk¶
Same filesystem
U3DP_SEEDINGS_DIR must live on the same device as U3DP_MEDIA_ROOT, otherwise hardlink creation fails with OSError: [Errno 18] Invalid cross-device link. Check with df <media> <seedings>.
WSL dev — dry-run without polluting the tracker¶
The wizard runs setenv → scan → maketorrent → seed end-to-end but skips /upload. Useful for testing pre-flight + bridge changes without creating real uploads.