CLI Commands
Generate Configuration File
Create a default configuration file without starting the server:
# Generate config in OS-specific default location
./qui generate-config
# Generate config in custom directory
./qui generate-config --config-dir /path/to/config/
# Generate config with custom filename
./qui generate-config --config-dir /path/to/myconfig.toml
User Management
Create and manage user accounts from the command line:
# Create initial user account
./qui create-user --username admin --password mypassword
# Create user with prompts (secure password input)
./qui create-user --username admin
# Change password for existing user (no old password required)
./qui change-password --username admin --new-password mynewpassword
# Change password with secure prompt
./qui change-password --username admin
# 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
# All commands support custom config/data directories
./qui create-user --config-dir /path/to/config/ --username admin
Notes
- Only one user account is allowed in the system
- Passwords must be at least 8 characters long
- Interactive prompts use secure input (passwords are masked)
- Supports piped input for automation and scripting
- Commands will create the database if it doesn't exist
- No password confirmation required - perfect for automation
Reset a Forgotten Password
If you've forgotten your password, use the change-password command to set a new one. No old password is required.
Linux / macOS:
./qui change-password --username admin --new-password mynewpassword
Windows (Command Prompt):
Navigate to the folder containing qui.exe and run:
qui.exe change-password --username admin --new-password mynewpassword
Docker:
docker exec -it <container-name> qui change-password --username admin --new-password mynewpassword
Replace admin with your username and mynewpassword with your desired password (minimum 8 characters).
Update Command
Keep your qui installation up-to-date:
# Update to the latest version
./qui update
Command Line Flags
# Specify config directory (config.toml will be created inside)
./qui serve --config-dir /path/to/config/
# Specify data directory for database and other data files
./qui serve --data-dir /path/to/data/
Database Migration
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:
- Run this while qui is stopped.
- Create the target Postgres database before running migration.
--dry-runand--applyare mutually exclusive.- The command copies all runtime tables except migration history.
- The migrator bootstraps schema/tables inside the destination DB, but does not create the database itself.
- The output includes per-table row counts for SQLite and Postgres.
FAQ
Q: Why is cross_seed_feed_items row count lower in Postgres after migration?
This is expected when the SQLite file contains historical rows whose indexer_id no longer exists in torznab_indexers.
Postgres enforces the foreign key strictly, so migration keeps only rows that still have valid parent records.
You can verify 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), migration behavior is working as intended.