CLI Commands
serve
Start the server:
./qui serve
# Custom config directory (config.toml is created inside)
./qui serve --config-dir /path/to/config/
# Custom data directory for the database and other data files
./qui serve --data-dir /path/to/data/
# Log to a file instead of stdout
./qui serve --log-path /path/to/qui.log
# Enable the pprof server (default 127.0.0.1:6060, override with QUI__PPROF_ADDR)
./qui serve --pprof
version
Print the version of qui:
./qui version
generate-config
Create a default configuration file without starting the server:
# Generate config in the OS-specific default location
./qui generate-config
# Generate config in a custom directory
./qui generate-config --config-dir /path/to/config/
# Generate config with a custom filename
./qui generate-config --config-dir /path/to/myconfig.toml
If the file already exists, the command keeps it and does not overwrite it.
create-user and change-password
Create and manage the user account from the command line:
For normal use, omit the password flag. qui prompts for the password and masks your input. Your shell can save --password or --new-password values in its history. Other users on the system may see those values in the process list.
# Create a user with a masked password prompt
./qui create-user --username admin
# Change the password with a masked prompt
./qui change-password --username admin
# Both commands accept password flags
./qui create-user --username admin --password mypassword
./qui change-password --username admin --new-password mynewpassword
# Pipe passwords for scripting (works with both commands)
echo "mypassword" | ./qui create-user --username admin
echo "newpassword" | ./qui change-password --username admin
printf "password" | ./qui change-password --username admin
./qui change-password --username admin < password.txt
# Both commands accept custom config/data directories
./qui create-user --config-dir /path/to/config/ --username admin
Notes
- The system allows only one user account.
- Passwords must be at least 8 characters long.
- Interactive prompts mask the password.
- Both commands accept piped input for automation and scripting.
- A piped password must not contain spaces. Type passwords with spaces at the interactive prompt.
- If the database does not exist,
create-usercreates it.change-passwordrequires an existing database. - The commands do not ask you to confirm the password.
Reset a forgotten password
If you forgot your password, set a new one with the change-password command. The command does not ask for the old password.
Linux / macOS:
./qui change-password --username admin
Windows (Command Prompt):
Open the folder that contains qui.exe and run:
qui.exe change-password --username admin
Docker:
docker exec -it <container-name> qui change-password --username admin
Replace admin with your username. Enter a new password of at least 8 characters at the prompt.
update
Update qui to the latest release:
./qui update
This command replaces the qui binary in place. For Docker, pull a new image instead (see Docker).
Migrate From Other Torrent Clients
Import torrents with their state from Deluge, rTorrent, or Transmission into qBittorrent's BT_backup directory. See Client Migration for per-client details and what qui preserves.
# Preview what the import would do, without writing anything
./qui migrate transmission \
--source-dir ~/.config/transmission-daemon \
--qbit-dir ~/.local/share/qBittorrent/BT_backup \
--dry-run
# Deluge: point at the state dir inside the Deluge config dir
./qui migrate deluge \
--source-dir ~/.config/deluge/state \
--qbit-dir ~/.local/share/qBittorrent/BT_backup
# rTorrent: point at the session dir from your .rtorrent.rc
./qui migrate rtorrent \
--source-dir ~/.sessions \
--qbit-dir ~/.local/share/qBittorrent/BT_backup
# Skip the automatic tar.gz backup of both directories
./qui migrate transmission --source-dir ... --qbit-dir ... --skip-backup
Notes:
- Stop the source client and qBittorrent before you migrate. Start qBittorrent afterwards, and it picks up the imported torrents.
- qui imports only fully downloaded torrents. It skips partial torrents with a warning, so no incorrect piece state reaches qBittorrent.
- qui preserves these fields per torrent: save path, trackers, upload/download totals, added/completed timestamps, seeding time, paused state (Deluge 2.x only; Deluge 1.3.x imports start resumed), Transmission labels (as qBittorrent tags), Deluge and ruTorrent labels (as the qBittorrent category).
- qui supports these source versions: Transmission 2.4-4.x, Deluge 1.3.x and 2.x, rTorrent 0.9.x through 0.16.x.
- If you do not set
--skip-backup, qui first archives both directories toqbt_backup/in the current working directory. If the qBittorrent directory already exists, qui archives it. A fresh destination produces only the source archive. - If a torrent already exists in the target
BT_backup, qui skips it. You can run the command again.
Database migration (db migrate)
Offline SQLite to Postgres migration:
# 0) Stop qui first (no writes during migration)
# (example) docker compose stop qui
# 1) Create the target Postgres database first (required)
# (example) createdb -h localhost -p 5432 -U user qui
# (or in psql) CREATE DATABASE qui;
# 2) Optional: backup the SQLite file
cp /path/to/qui.db /path/to/qui.db.bak
# 3) Validate source + destination without importing rows
./qui db migrate \
--from-sqlite /path/to/qui.db \
--to-postgres "postgres://user:pass@localhost:5432/qui?sslmode=disable" \
--dry-run
# 4) Apply migration (schema bootstrap + table copy + identity reset)
./qui db migrate \
--from-sqlite /path/to/qui.db \
--to-postgres "postgres://user:pass@localhost:5432/qui?sslmode=disable" \
--apply
# 5) Point qui at Postgres and start it again
# - config.toml: databaseEngine=postgres + databaseDsn=...
# - or env: QUI__DATABASE_ENGINE=postgres + QUI__DATABASE_DSN=...
Notes:
- Stop qui before you run the migration.
- Create the target Postgres database before you run the migration.
- Set exactly one of
--dry-runor--apply. - The command copies all runtime tables except migration history.
- The migrator creates the schema and tables inside the destination database. It does not create the database itself.
- The output includes per-table row counts for SQLite and Postgres.
- On a new database, the dry run lists every table under
Missing Postgres tableswithpostgres=0. This is expected.--applycreates them. --applyempties every table in the destination database before it copies rows. Point it only at a new or empty database. If qui already ran against that Postgres database, its data is lost. A non-zeropostgres=count in the dry run output means the destination is not empty.
FAQ
Q: Why is cross_seed_feed_items row count lower in Postgres after migration?
If the SQLite file contains historical rows whose indexer_id no longer exists in torznab_indexers, the migration skips them. Postgres enforces the foreign key, so the migration keeps only rows that have valid parent records.
You can check this in SQLite:
SELECT COUNT(*) AS orphaned_rows
FROM cross_seed_feed_items f
LEFT JOIN torznab_indexers i ON i.id = f.indexer_id
WHERE i.id IS NULL;
If orphaned_rows matches the migration delta (sqlite_count - postgres_count), the migration works as intended.