Deployment › VPS (sudo / Docker)¶
Guide for a generic Linux VPS with sudo privileges: Debian/Ubuntu/Arch/any systemd-based distro. If you are on Ultra.cc go to the Ultra.cc guide instead: no sudo and nginx user-proxy.
Target architecture: two systemd services sharing the same .env.
unit3dprep-web.service ─┐ ┌─> tracker (HTTPS)
│ │
(FastAPI + UI) │ HTTP+WS │
├──────────────> unit3dwebup.service ─┘
│ (port 8000) │
│ ├─> qBittorrent (host:port)
│ │
│ └─> Redis 127.0.0.1:6379
│
└── shared .env in $ENVPATH/.env
Two scenarios:
- Native with systemd + nginx + Let's Encrypt (recommended — less overhead, more control).
- Docker / docker-compose (if you prefer containers).
1 — System prerequisites¶
Debian/Ubuntu:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip \
libmediainfo0v5 ffmpeg redis-server \
nginx git \
certbot python3-certbot-nginx
sudo systemctl enable --now redis-server
ffmpeg and redis-server are required by Unit3DWebUp. Redis must stay on 127.0.0.1:6379 (webup hardcodes it; REDIS_HOST/PORT env vars are ignored).
Create a dedicated user (do not run services as root):
sudo adduser --system --group --shell /bin/bash --home /opt/unit3dprep unit3dprep
sudo -u unit3dprep -i
All subsequent steps run as unit3dprep unless stated otherwise.
2 — Install unit3dprep + Unit3DWebUp¶
Same venv (simpler to maintain — one pip install --upgrade updates both):
cd ~
git clone https://github.com/davidesidoti/unit3dprep.git
cd unit3dprep
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
pip install Unit3DwebUp
Or two separate venvs (handy if you want to update the two packages independently):
python3 -m venv ~/.venvs/unit3dprep && ~/.venvs/unit3dprep/bin/pip install -e ~/unit3dprep
python3 -m venv ~/.venvs/unit3dwebup && ~/.venvs/unit3dwebup/bin/pip install Unit3DwebUp
Generate password hash and secret:
Copy the output into an env file systemd will load (step 4).
Create the media folders:
mkdir -p ~/media/{movies,series,anime} ~/seedings ~/.config/unit3dprep
df ~/media ~/seedings # verify same filesystem
3 — Initial shared .env¶
Bootstrap the shared .env with minimal secrets. You'll add tracker/qBit credentials later via the Web UI:
cat > ~/.config/unit3dprep/.env <<'EOF'
# Auth
U3DP_PASSWORD_HASH='$2b$12$...'
U3DP_SECRET=hex-secret
TMDB_API_KEY=your-tmdb-key
# Web UI
U3DP_HOST=127.0.0.1
U3DP_PORT=8765
U3DP_HTTPS_ONLY=1
# Webup bridge (defaults — change only if needed)
WEBUP_URL=http://127.0.0.1:8000
EOF
chmod 600 ~/.config/unit3dprep/.env
Single quotes around U3DP_PASSWORD_HASH
The bcrypt hash contains $. Without single quotes bash expands $2b/$12 as empty variables → mutilated hash → silent 401 login.
4 — Systemd units (system)¶
unit3dwebup.service¶
Create /etc/systemd/system/unit3dwebup.service:
[Unit]
Description=Unit3DWebUp FastAPI bot
After=network-online.target redis.service
Wants=network-online.target
Requires=redis.service
[Service]
Type=simple
User=unit3dprep
Group=unit3dprep
WorkingDirectory=/opt/unit3dprep/unit3dprep
# DO NOT set DOCKER here — webup's settings.py uses a bare-truthy check.
Environment=PYTHONUNBUFFERED=1
Environment=ENVPATH=/opt/unit3dprep/.config/unit3dprep
ExecStart=/opt/unit3dprep/unit3dprep/.venv/bin/uvicorn unit3dwup.start:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
unit3dprep-web.service¶
Create /etc/systemd/system/unit3dprep-web.service:
[Unit]
Description=unit3dprep web UI
After=network-online.target unit3dwebup.service
Wants=network-online.target
Requires=unit3dwebup.service
[Service]
Type=exec
User=unit3dprep
Group=unit3dprep
WorkingDirectory=/opt/unit3dprep/unit3dprep
EnvironmentFile=/opt/unit3dprep/.config/unit3dprep/.env
Environment=ENVPATH=/opt/unit3dprep/.config/unit3dprep
Environment=U3DP_SYSTEMD_UNIT=unit3dprep-web.service
Environment=WEBUP_SYSTEMD_UNIT=unit3dwebup.service
Environment=WEBUP_VENV_BIN=/opt/unit3dprep/unit3dprep/.venv/bin
ExecStart=/opt/unit3dprep/unit3dprep/.venv/bin/unit3dprep-web
Restart=on-failure
RestartSec=5
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/opt/unit3dprep
[Install]
WantedBy=multi-user.target
Never set DOCKER
Webup config/settings.py does env_file=ENV_FILE if not os.getenv("DOCKER") else None. That's a bare truthy check on string: DOCKER=false → not "false" → False → env_file=None → pydantic-settings ignores the .env → every TRACKER__/PREFS__ field reports "Field required". Only set DOCKER=true when you really are in Docker; otherwise omit it.
Enable and start (the Requires ordering ensures webup starts first):
sudo systemctl daemon-reload
sudo systemctl enable --now unit3dwebup.service
sudo systemctl enable --now unit3dprep-web.service
sudo systemctl status unit3dwebup.service unit3dprep-web.service
journalctl -u unit3dprep-web.service -f
Bot smoke test:
curl -s -X POST http://127.0.0.1:8000/setting -H 'Content-Type: application/json' -d '{}' | head -c 200
Must return {"userPreferences": ...}.
5 — Nginx reverse proxy + HTTPS¶
Only unit3dprep-web is publicly exposed. unit3dwebup stays on 127.0.0.1:8000.
/etc/nginx/sites-available/unit3dprep.conf:
server {
listen 80;
server_name unit3dprep.example.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name unit3dprep.example.com;
# Certificates populated by certbot
ssl_certificate /etc/letsencrypt/live/unit3dprep.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/unit3dprep.example.com/privkey.pem;
# SSE-friendly timeouts and buffering
proxy_buffering off;
proxy_read_timeout 1h;
# Allow large uploads
client_max_body_size 4g;
location / {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection ""; # SSE / keep-alive
}
}
Enable + obtain the certificate:
sudo ln -s /etc/nginx/sites-available/unit3dprep.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d unit3dprep.example.com
Subpath vs root
The example serves at the domain root. If you want a subpath (e.g. unit3dprep.example.com/unit3dprep/), add a trailing slash to proxy_pass (proxy_pass http://127.0.0.1:8765/;) so nginx strips the prefix → leave U3DP_ROOT_PATH="". Or keep nginx non-stripping (no trailing slash) and set U3DP_ROOT_PATH=/unit3dprep.
6 — Firewall¶
Keep ports 8765 and 8000 closed: services listen only on 127.0.0.1.
7 — Initial configuration via Web UI¶
- Open
https://unit3dprep.example.com, log in. - Go to Settings → Trackers and enter URL/API key/PID for ITT (and PTT/SIS if used).
- Torrent client → host/port/credentials for qBittorrent (or Transmission/rTorrent).
- Image hosts → at least one key (PTSCREENS, IMGBB, IMGFI, etc.) and order the list in
IMAGE_HOST_ORDER. - Metadata → confirm
TMDB_APIKEY, optionally TVDB. - Save → each key is written to
$ENVPATH/.envwith canonical naming and propagated tounit3dwebupviaPOST /setenv(no restart).
Verify the Unit3DWebUp card in Settings: must be green with version and ms latency.
8 — Backups¶
Files to back up periodically:
~/.config/unit3dprep/.env # secrets + full config
~/.unit3dprep_db.json # upload history
~/.unit3dprep_tmdb_cache.json # regeneratable
~/.unit3dprep_lang_cache.json # regeneratable
Example with rsync + cron:
9 — Updates¶
Via Web UI (recommended)¶
In Settings → Version click Install update on the App card or the Unit3DWebUp card. See Usage › Web UI.
Manual¶
sudo -u unit3dprep -i
cd ~/unit3dprep
git pull
source .venv/bin/activate
pip install -e .
pip install --upgrade Unit3DwebUp
exit
sudo systemctl restart unit3dwebup.service
sudo systemctl restart unit3dprep-web.service
If you touched the frontend, rebuild it (requires Node):
Docker variant¶
The repo ships a ready-to-use all-in-one image (Redis + Unit3DWebUp + unit3dprep in a
single container) with a Dockerfile and docker-compose.yml. The full guide — clone, hash
generation, external qBittorrent, TLS reverse proxy and troubleshooting — lives on its own
page: Deploy › Docker.
In short:
git clone https://github.com/davidesidoti/unit3dprep.git
cd unit3dprep
cp config.env.example config.env # then fill in U3DP_PASSWORD_HASH / U3DP_SECRET / TMDB_API_KEY
docker compose build
docker compose up -d
# open http://127.0.0.1:8765
Post-deploy checklist¶
- [ ]
systemctl status unit3dwebup.service→active (running) - [ ]
systemctl status unit3dprep-web.service→active (running) - [ ]
curl -X POST http://127.0.0.1:8000/setting -d '{}' -H 'Content-Type: application/json'→ JSON withuserPreferences - [ ]
https://unit3dprep.example.comresponds, login works - [ ]
journalctl -u unit3dprep-web -u unit3dwebup -f→ no errors - [ ] Settings → Unit3DWebUp card green (online)
- [ ]
GET /api/settings/fs-check→same_fs: true - [ ] A test upload completes end-to-end (or
U3DP_DRY_RUN_TRACKER=1for dry-run) - [ ] Automatic backup configured
- [ ]
certbot renew --dry-run→ success