No description
  • Python 39.1%
  • Go 36.8%
  • Shell 20.8%
  • Makefile 3.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dev-boffin-io ee903790ef Refactor: modularize GUI, fix qb-webui %-escaping bug, add status/logs/settings
- Fix qb-webui loading screen bug: CSS "%" was misparsed by fmt.Sprintf
  causing "%!s(MISSING)" to render. Switched to html/template.
- qb-webui: exponential backoff retries, new -autostart flag for
  standalone use without the GUI.
- qb-main: new status/start/stop/version subcommands (daemon.go) to
  manage qbittorrent-nox headlessly, e.g. over SSH.
- core/main.py split into config.py, netutil.py, qbit_process.py,
  theme.py, dialogs.py, main.py.
- GUI: new Settings dialog (zoom/window size/kiosk/start-hidden),
  new Logs dialog, desktop notifications on start/stop/restart.
- Fix: stdout was being closed right after password capture, risking
  SIGPIPE in the still-running daemon. Now drained continuously, which
  also feeds the new Logs dialog.
2026-07-02 09:17:32 +05:30
assets Initial commit: Added Project Structure 2026-03-23 12:44:11 +00:00
builder Refactor: modularize GUI, fix qb-webui %-escaping bug, add status/logs/settings 2026-07-02 09:17:32 +05:30
core Refactor: modularize GUI, fix qb-webui %-escaping bug, add status/logs/settings 2026-07-02 09:17:32 +05:30
go-src Refactor: modularize GUI, fix qb-webui %-escaping bug, add status/logs/settings 2026-07-02 09:17:32 +05:30
.gitignore Initial commit: Added Project Structure 2026-03-23 12:44:11 +00:00
install.sh Initial commit: Added Project Structure 2026-03-23 12:44:11 +00:00
LICENSE Initial commit: Added Project Structure 2026-03-23 12:44:11 +00:00
Makefile Initial commit: Added Project Structure 2026-03-23 12:44:11 +00:00
README.md Refactor: modularize GUI, fix qb-webui %-escaping bug, add status/logs/settings 2026-07-02 09:17:32 +05:30

qbit-forge

License: MIT Platform Go Python Status

A lightweight, privacy-first management suite for qbittorrent-nox on Debian Linux — including ARM64 and proot-Termux environments.

qbit-forge wraps qbittorrent-nox with a PyQt6 desktop GUI, a native WebUI launcher, and a package manager. Everything runs locally, offline-first, with no cloud dependency.


Table of Contents


Architecture

┌─────────────────────────────────────────┐
│           qbit-forge (GUI)              │  PyQt6 — system tray app
│   Start · Stop · Restart · WebUI        │
└──────────────┬──────────────────────────┘
               │ subprocess + poll
               ▼
┌──────────────────────┐   ┌──────────────────────┐
│  qbittorrent-nox     │──▶│  qb-webui            │
│  (managed process)   │   │  (GTK native webview)│
└──────────────────────┘   └──────────────────────┘

┌──────────────────────┐
│  qb-main             │  standalone CLI — install / upgrade / clean
└──────────────────────┘

Project Structure

qbit-forge/
├── assets/
│   └── qbit-forge.png          # Application icon
├── bin/                        # Built binaries (git-ignored)
│   ├── qbit-forge              # Desktop GUI binary
│   ├── qb-main                 # Package manager CLI
│   └── qb-webui                # Native WebUI launcher
├── builder/
│   ├── build-bin.sh            # Go build script (qb-main + qb-webui)
│   └── build-main.sh           # PyInstaller build script
├── core/
│   ├── main.py                 # GUI entry point (MainWindow, wiring)
│   ├── config.py                # Paths, constants, persisted settings
│   ├── netutil.py               # Port allocation, WebUI-readiness checks
│   ├── qbit_process.py          # Start/stop qbittorrent-nox, credentials, log buffer
│   ├── theme.py                  # Catppuccin Mocha stylesheet + tray icon
│   ├── dialogs.py                # Credentials / Settings / Logs windows
│   ├── qbit-forge.spec          # PyInstaller spec
│   └── requirements.txt         # Python dependencies
├── go-src/
│   ├── qb-main/                 # qb-main CLI source (Go)
│   │   ├── main.go              # CLI dispatch
│   │   ├── manager.go           # Install / Update / Upgrade / Clean
│   │   ├── daemon.go            # Status / Start / Stop / Version
│   │   ├── runner.go            # Privilege-aware command runner
│   │   ├── deb_install.go       # .deb fallback installer
│   │   └── print.go             # Colored output helpers
│   └── qb-webui/                # Native WebUI launcher source (Go)
│       ├── main.go              # webview wrapper, retry loop, autostart
│       ├── ui.go                 # Loading/error screen (html/template)
│       └── tls.go                # TLS config (-insecure flag)
├── install.sh                  # Symlink + desktop entry installer
├── Makefile                    # Build / install / clean targets
├── LICENSE                     # MIT
└── README.md

Requirements

Runtime

Dependency Version Purpose
qbittorrent-nox any The managed application
Python 3.10+ GUI runtime
PyQt6 6.4+ GUI framework

Build

Tool Version Purpose
Go 1.21+ Build qb-main, qb-webui
GCC / libwebkit2gtk — Build qb-webui (CGO=1)
PyInstaller 6.0+ Build standalone qbit-forge GUI binary

System (Debian/Ubuntu)

# qb-webui build dependency
sudo apt-get install libwebkit2gtk-4.0-dev gcc

# GUI runtime
pip install PyQt6

Quick Start

# 1. Clone
git clone https://github.com/dev-boffin-io/qbit-forge
cd qbit-forge

# 2. Install qbittorrent-nox (if not already installed)
./bin/qb-main install

# 3. Build everything
make build

# 4. Install (symlinks + desktop entry)
make install

# 5. Launch
qbit-forge

Build

All at once

make build

Go binaries only

make build-bin

# Cross-compile for ARM64
./builder/build-bin.sh --arch arm64

# Specific OS/arch
./builder/build-bin.sh --os linux --arch amd64

qb-main is built with CGO=0 (fully static, cross-compile supported). qb-webui is built with CGO=1 (requires host C compiler and libwebkit2gtk).

GUI binary only

make build-gui

The build script creates a fresh .venv, installs dependencies, runs PyInstaller, and cleans up. Only bin/qbit-forge remains.

Run from source

pip install -r core/requirements.txt
make run

Install

make install

This runs install.sh which:

  1. Symlinks qbit-forge, qb-main, qb-webui into ~/.local/bin/
  2. Copies assets/qbit-forge.png to ~/.local/share/icons/
  3. Writes ~/.local/share/applications/qbit-forge.desktop
  4. Refreshes the desktop database

To uninstall:

make uninstall

Components

qbit-forge — Desktop GUI

A PyQt6 system tray application that manages a single qbittorrent-nox instance.

Features:

  • Lives in the system tray — minimises to tray on close
  • Auto-starts qbittorrent-nox on launch
  • Captures the WebUI password automatically from process output
  • Caches credentials so subsequent starts skip the capture step
  • Auto-detects a free port starting from 8081
  • Opens qb-webui (native window) or browser fallback on ready
  • Start / Stop / Restart controls with confirmation dialogs
  • Show Password dialog — displays username, password, and WebUI URL
  • Settings dialog — WebUI zoom, window size, kiosk mode, start-hidden toggle
  • Logs dialog — live-tails qbittorrent-nox output from inside the GUI
  • Desktop notifications on start/stop/restart completion (via the tray icon)
  • Continuously drains the daemon's stdout instead of closing it after the password is captured, avoiding a SIGPIPE-on-write risk for long sessions

State files (stored in $HOME):

File Purpose
~/.qbit-forge-initialized First-run flag
~/.qbit-forge-credentials Cached username:password
~/.qbit-forge-port Active WebUI port
~/.qbit-forge-settings.json Zoom / window size / kiosk / start-hidden

qb-webui — Native WebUI Launcher

Opens the qBittorrent WebUI in a native GTK webview window. Built with webview/webview_go.

Usage:
  qb-webui [flags]

Flags:
  -url        string   WebUI URL (e.g. http://localhost:8081)
  -host       string   Host                    (default: localhost)
  -port       int      WebUI port              (default: 8081)
  -scheme     string   http or https           (default: http)
  -retries    int      Connection retry limit  (default: 15)
  -width      int      Window width in pixels  (default: 1600)
  -height     int      Window height in pixels (default: 960)
  -zoom       float    Page zoom level         (default: 1.0)
  -kiosk               Fullscreen kiosk mode
  -insecure            Skip TLS verification
  -autostart           If unreachable, try launching qbittorrent-nox
                        directly (PATH lookup). Lets qb-webui work
                        stand-alone without the qbit-forge GUI running it.

Examples:

qb-webui -url http://localhost:8081
qb-webui -url http://localhost:8081 -zoom 1.8
qb-webui -url https://myserver -insecure

# Stand-alone, without the GUI:
qb-webui -port 8081 -autostart

A Catppuccin Mocha loading screen is shown while connecting, with retries using exponential backoff (500ms → 3s cap) instead of a flat 1s poll. On failure, an error card appears with a Retry button.

Fixed: the loading screen used to be built with fmt.Sprintf directly on the HTML/CSS string. A literal % in the CSS (border-radius: 50%;) was misparsed as a format verb, corrupting the page with %!s(MISSING). It's now rendered with html/template, which has no such issue.

Reminder: qb-webui only displays the WebUI — it does not start qbittorrent-nox for you unless -autostart is passed. Normally the qbit-forge GUI starts qbittorrent-nox first and launches qb-webui once it's ready.


qb-main — Package Manager

Handles installation, upgrade, and removal of qbittorrent-nox.

Usage:
  qb-main install            Install qbittorrent-nox (apt first, .deb fallback)
  qb-main update              Refresh apt cache
  qb-main upgrade             Upgrade to latest available version
  qb-main reinstall           Remove and cleanly reinstall qbittorrent-nox
  qb-main clean               Uninstall qbittorrent-nox + remove instance data

  qb-main status              Show whether qbittorrent-nox is running
  qb-main start [--port N]    Start qbittorrent-nox (headless, no GUI needed)
  qb-main stop                Stop the running instance

  qb-main version             Print qb-main and qbittorrent-nox versions

install strategy: tries apt-get install first; if apt is unavailable or fails, falls back to downloading a .deb directly from ftp.debian.org.

upgrade strategy: runs apt-get install --only-upgrade; falls back to .deb download if apt is unavailable.

clean: purges the package via apt/dpkg and removes ~/.config/qb-manager/.

status / start / stop: manage the same qbittorrent-nox instance the qbit-forge GUI manages — both read/write ~/.qbit-forge-port, so you can start it from the GUI and check on it from a terminal (or vice versa), including headless over SSH where there's no display for the GUI at all.


How It Works

First run

On the very first launch, qbit-forge spawns qbittorrent-nox in the foreground, reads its stdout to capture the auto-generated temporary password, saves it to ~/.qbit-forge-credentials, and marks initialization complete with ~/.qbit-forge-initialized.

Subsequent runs

Credentials are read from ~/.qbit-forge-credentials. qbittorrent-nox is started in daemon mode (-d) with the saved port. The GUI polls the WebUI endpoint until it responds, then opens qb-webui.

Password capture

qbittorrent-nox prints the temporary WebUI password to stdout on first start. qbit-forge reads this line with a regex, saves admin:<password> to the credentials file, and uses it for all subsequent API calls and the Show Password dialog.

Port detection

On each start, qbit-forge scans ports from 8081 upward and picks the first free one. The chosen port is saved to ~/.qbit-forge-port.


Known Limitations / TODO

  • Password capture relies on stdout — versions that write only to /dev/tty fall back to adminadmin
  • qb-main only supports Debian/dpkg-based systems
  • qb-webui kiosk mode relies on window manager co-operation
  • No torrent management UI (add, remove, pause individual torrents)
  • GUI binary not yet signed or packaged as .deb / AppImage
  • qb-webui's loading screen could be corrupted by % in its CSS — fixed, now uses html/template
  • Closing stdout right after password capture risked SIGPIPE in a live daemon — fixed, stdout is drained continuously instead

Author

Sumit — github.com/dev-boffin-io