Jesse A. Green / notebook

Motrix Admin & Developer Guide

Generated June 2026 from agalwood/Motrix at 7012040

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the complete operational and architectural reference for Motrix — a cross-platform desktop download manager built on Electron, Vue, and the aria2 engine. It is written for administrators, packagers, security reviewers, and developers who need to understand how Motrix is built, configured, secured, deployed, and extended.

Everything here is derived directly from the Motrix source tree at commit 7012040 (version 1.8.19, dated 2023-06-07). Where a behavior carries a security or operational caveat, it is documented honestly rather than glossed over.

Note

For end-user instructions (installing, adding downloads, managing tasks), see the companion User Guide at User Guide.


Table of Contents

  1. Executive Summary
  2. Architecture
  3. System Requirements
  4. Configuration Mastery
  5. The aria2 Engine Option Reference
  6. Security Model
  7. Operations
  8. Build & Distribution
  9. Module Reference (Internals)
  10. Code Quality & Patterns
  11. Troubleshooting (Operational)
  12. Potential Improvements
  13. Quick Reference

1. Executive Summary

Motrix is an Electron desktop application that wraps the aria2 command-line download engine in a Vue-based UI. It is a single-user, offline-first desktop tool — there is no server component, no multi-user mode, and no cloud backend.

Attribute Value
Product Motrix — "A full-featured download manager"
Version (this doc) 1.8.19
License MIT (© 2018–present Dr_rOot)
App ID (bundle) app.motrix.native
Platforms Windows (x64/ia32), macOS (x64/arm64/universal), Linux (x64/arm64/armv7l)
Desktop framework Electron ^22.3.9
UI framework Vue 2.7.14 + Vuex 3.6.2 + vue-router 3.6.5 + Element UI 2.15.13
Download engine Bundled aria2 (aria2c), driven over JSON-RPC
i18n 25 locales via @panter/vue-i18next / i18next
Build system Webpack 5 + electron-builder 24 (electron-vue project layout)
Node (build) >= 16.0.0

Architectural one-liner: A privileged Electron main process spawns and supervises a bundled aria2c child process, then both the main process and the Vue renderer drive that engine over a local JSON-RPC websocket. Persistent state lives in two electron-store JSON files (system + user) plus aria2's own session file.

Important

Motrix uses Electron's older, less-sandboxed security model (@electron/remote enabled, no context isolation, direct ipcRenderer use). Combined with the bundled aria2's RPC defaults (rpc-listen-all=true, empty secret), this has real security implications on shared networks. See §6 Security Model — this is the most important section for reviewers.


2. Architecture

2.1 Process model

Motrix follows the classic Electron two-process split, plus a third, external process: the aria2 engine.

                          ┌─────────────────────────────────────────────┐
                          │              Motrix (Electron)               │
                          │                                              │
   OS events              │   ┌──────────────────┐   IPC: 'command' /   │
  (open-url,    ───────────►  │   MAIN PROCESS    │   'event' channels   │
   open-file,   argv      │   │   (Node.js)       │◄────────┐            │
   protocol)              │   │                   │         │            │
                          │   │  Launcher         │   ┌─────┴─────────┐  │
                          │   │  Application      │   │  RENDERER     │  │
                          │   │  ├ ConfigManager  │   │  (Chromium)   │  │
                          │   │  ├ Engine ────────┼───┤  Vue 2 +      │  │
                          │   │  ├ EngineClient   │   │  Vuex +       │  │
                          │   │  ├ WindowManager  │   │  Element UI   │  │
                          │   │  ├ Tray/Menu/Dock │   │               │  │
                          │   │  ├ UPnPManager    │   │  api/Api.js ──┼──┼──┐
                          │   │  ├ ProtocolManager│   │  store/*      │  │  │
                          │   │  └ UpdateManager  │   └───────────────┘  │  │
                          │   └─────────┬─────────┘                      │  │
                          └─────────────┼────────────────────────────────┘  │
                                        │ spawn() child process              │
                                        ▼                                    │
                          ┌─────────────────────────┐   JSON-RPC over        │
                          │   aria2c (bundled)       │◄──ws 127.0.0.1:16800───┘
                          │   --conf-path=aria2.conf │   (both processes
                          │   --save-session=…       │    connect to the
                          └─────────────────────────┘    same RPC endpoint)

Three independent connections to the engine exist:

  1. Main process → engine (EngineClient) — used for global option changes, shutdown, and resume-on-launch.
  2. Renderer → engine (api/Api.js) — the primary path: add/pause/resume/remove tasks, poll status, fetch peers.
  3. The engine reads its session file to restore tasks across restarts.

2.2 Component inventory

Component File Responsibility
Launcher src/main/Launcher.js Single-instance lock, app lifecycle (ready/activate/will-quit), URL/file/argv intake, @electron/remote init
Application src/main/Application.js The orchestrator: instantiates all managers, wires IPC, watches config changes, drives the engine
ConfigManager src/main/core/ConfigManager.js Two electron-store stores (system + user) with defaults, fix-up, get/set/reset
Engine src/main/core/Engine.js Spawns/stops the aria2c child process; builds its CLI args from config
EngineClient src/main/core/EngineClient.js Main-process JSON-RPC client (call, changeGlobalOption, shutdown)
WindowManager src/main/ui/WindowManager.js Creates/shows/hides BrowserWindows, persists window state
MenuManager / TrayManager / DockManager / TouchBarManager / ThemeManager src/main/ui/* Native UI surfaces (app menu, tray, dock, Touch Bar, theme)
UPnPManager src/main/core/UPnPManager.js UPnP/NAT-PMP port mapping via @motrix/nat-api
ProtocolManager src/main/core/ProtocolManager.js Registers mo/motrix/magnet/thunder schemes; routes incoming URLs
UpdateManager src/main/core/UpdateManager.js electron-updater integration, proxy-aware, manual download/install
AutoLaunchManager src/main/core/AutoLaunchManager.js Open-at-login via app.setLoginItemSettings
EnergyManager src/main/core/EnergyManager.js powerSaveBlocker while downloading
ExceptionHandler src/main/core/ExceptionHandler.js Global uncaughtException handler + error dialog
Logger src/main/core/Logger.js electron-log wrapper, portable-aware path
Renderer API src/renderer/api/Api.js Renderer-side aria2 RPC client + config bridge
Vuex store src/renderer/store/modules/* app, task, preference state modules
Aria2 RPC lib src/shared/aria2/lib/* Minimal JSON-RPC client (Aria2, JSONRPCClient, Deferred)
Shared constants/config keys src/shared/constants.js, configKeys.js Enums, ports, key lists

2.3 Startup sequence

From Application.init(), in order: Context → ConfigManager → Logger → LocaleManager → application menu → WindowManager → UPnPManager → start engine → EngineClient → ThemeManager → TrayManager → TouchBarManager → DockManager → AutoLaunchManager → EnergyManager → ProtocolManager → UpdaterManager → command handlers → event handlers → IPC message/invoke handlers → emit application:initialized.

On initialized, Motrix runs auto-sync trackers, auto-resume tasks (if enabled), and adjusts the menu for the Mac App Store build.

2.4 The IPC surface (the "API")

There is no HTTP API. The renderer↔main contract is three IPC channels, registered in Application.handleIpcMessages() / handleIpcInvokes():

Channel Direction Payload Purpose
command renderer → main (command, ...args) Fire an application command; re-emitted on the Application EventEmitter
event renderer → main (eventName, ...args) Fire an internal event
get-app-config renderer → main (invoke) — → merged config object Returns { ...systemConfig, ...userConfig, ...context }

Commands are plain string event names (e.g. application:save-preference, application:new-task, application:quit, application:reveal-in-folder, application:factory-reset). The full set is registered in Application.handleCommands().

Warning

Because the renderer can emit any command string over the command channel and the main process re-emits it on its EventEmitter, the IPC boundary is effectively unrestricted. This is the basis of GitHub issue #1796 ("Unrestricted IPC command emission allows arbitrary shell.openExternal and internal command invocation"). See §6.3.


3. System Requirements

3.1 Runtime (end-user) requirements

Platform Minimum
Windows Windows 7+ (Scoop build notes Win7+); x64 or ia32
macOS A macOS version supported by Electron 22
Linux glibc-based distro; x64, arm64, or armv7l. AppImage needs FUSE / AppImageLauncher

Disk: enough for the app (~150–200 MB installed) plus your downloads. Network: open BT/DHT listen ports for optimal BitTorrent performance (UPnP can handle this automatically).

3.2 Build requirements

Tool Version
Node.js >= 16.0.0
Package manager Yarn (project is yarn-locked)
Electron ^22.3.9
electron-builder ^24.4.0
Python / build tools As required by native deps (@motrix/nat-api, etc.)

Note

The project is structured as an electron-vue app: build orchestration lives in .electron-vue/ (build.js, dev-runner.js, webpack configs), invoked by the package.json scripts. The postinstall step runs electron-builder install-app-deps to rebuild native modules against Electron's ABI.


4. Configuration Mastery

Motrix keeps two configuration stores, both as electron-store JSON files in the per-user data directory. Understanding the split is essential.

4.1 The system / user split

Store File Holds Consumed by
System config system.json aria2 engine options (downloads, BT, ports, RPC, proxy) Passed to aria2 as CLI args / changeGlobalOption
User config user.json App preferences (theme, locale, run mode, startup, UPnP) The Electron app itself

ConfigManager (src/main/core/ConfigManager.js) owns both. The key lists live in src/shared/configKeys.js:

  • userKeys — 28 app-preference keys.
  • systemKeys — ~110 aria2 option keys (the allow-list of options Motrix will pass to the engine).
  • needRestartKeys — keys that require an engine/app restart: dht-listen-port, hide-app-menu, listen-port, rpc-listen-port, rpc-secret.

Important

aria2 configuration priority: system.json overrides the built-in aria2.conf, which is documented in the file header (system.json > built-in aria2.conf). ConfigManager.fixSystemConfig() deletes any stored key that isn't a recognized aria2 option (separateConfig().others), so the engine never receives garbage flags.

4.2 User config reference (defaults)

From ConfigManager.initUserConfig():

Key Default Meaning
auto-check-update is.macOS() Auto-check for updates (on by default only on macOS)
auto-hide-window false Hide window on blur
auto-sync-tracker true Daily tracker-list sync
enable-upnp true UPnP/NAT-PMP port mapping
engine-max-connection-per-server 64 UI cap for per-task splits
favorite-directories [] Pinned save folders (max 5)
hide-app-menu `is.windows()
history-directories [] Recent save folders (max 5)
keep-seeding false Seed indefinitely
keep-window-state false Persist window bounds
last-check-update-time 0 Bookkeeping
last-sync-tracker-time 0 Bookkeeping
locale app.getLocale() UI language
log-level 'warn' electron-log file level
new-task-show-downloading true Jump to Downloading after add
no-confirm-before-delete-task false Skip delete confirmation
open-at-login false Launch at login
protocols { magnet: true, thunder: false } Default-handler registration
proxy { enable:false, server:'', bypass:'', scope:[download,update-app,update-trackers] } App/engine proxy
resume-all-when-app-launched false Auto-resume on launch
run-mode 1 (Standard) Standard / Tray / Hide-Tray
show-progress-bar true Taskbar/dock progress bar
task-notification true Desktop notifications
theme 'auto' auto / light / dark
tracker-source ngosang best + best_ip (CDN) Tracker list sources
tray-theme 'auto' Tray icon theme
tray-speedometer is.macOS() Menu-bar speed meter (macOS)
update-channel 'latest' electron-updater channel
window-state {} Persisted window bounds per page

4.3 System config defaults (subset shipped in system.json)

ConfigManager.initSystemConfig() seeds these defaults (the rest fall through to the built-in aria2.conf):

Key Default
dht-listen-port 26701
listen-port 21301
rpc-listen-port 16800 (ENGINE_RPC_PORT)
rpc-secret '' (empty)
max-concurrent-downloads 5
max-connection-per-server 64
split 64
max-download-limit / max-overall-download-limit / max-overall-upload-limit 0 (unlimited)
seed-ratio 2
seed-time 2880 (minutes)
continue true
auto-file-renaming true
follow-metalink / follow-torrent true
bt-load-saved-metadata / bt-save-metadata true
enable-dht6 true
pause true
user-agent Chrome UA string
all-proxy / no-proxy / bt-tracker / bt-exclude-tracker ''
dht-file-path / dht-file-path6 dht.dat / dht6.dat in user data

Note

Mock User-Agent presets are defined in src/shared/ua.js: aria2/1.36.0, Transmission/3.00, a Chrome 111 UA (the default), and a Baidu NetDisk UA. The Chrome UA is used by default so generic servers don't reject the engine.

4.4 Proxy configuration

Proxy is a structured user-config object with scopes (src/shared/constants.js → PROXY_SCOPES):

Scope Applies to
download aria2 downloads (sets all-proxy / no-proxy on the engine)
update-app electron-updater traffic (via updater.netSession.setProxy)
update-trackers Tracker-list fetches

When proxy.enable && server && scope.includes('download'), Application.watchProxyChange() pushes { 'all-proxy': server, 'no-proxy': bypass } into the system config and calls changeGlobalOption live. The updater parses credentials from a user:pass@host URL and registers a login handler for authenticated proxies.


5. The aria2 Engine Option Reference

Motrix passes engine options to aria2 two ways: at spawn time (Engine.getStartArgs() → transformConfig() renders each non-empty key as --key=value) and live (changeGlobalOption). The complete allow-list of options Motrix recognizes is systemKeys in src/shared/configKeys.js. Any of these may be set in system.json.

5.1 Built-in aria2.conf baseline

Each platform ships an aria2.conf under extra/{platform}/{arch}/engine/. The baseline (Windows x64) sets:

Group Settings
RPC enable-rpc=true, rpc-allow-origin-all=true, rpc-listen-all=true
Filesystem auto-save-interval=10, disk-cache=64M, file-allocation=falloc, no-file-allocation-limit=64M, save-session-interval=10
Task bt-detach-seed-only=true, check-certificate=false, max-file-not-found=10, max-tries=0, retry-wait=10, connect-timeout=10, timeout=10, min-split-size=1M, http-accept-gzip=true, remote-time=true, summary-interval=0, content-disposition-default-utf8=true
BitTorrent bt-enable-lpd=true, bt-hash-check-seed=true, bt-max-peers=128, bt-prioritize-piece=head, bt-remove-unselected-file=true, bt-seed-unverified=false, bt-tracker-connect-timeout=10, bt-tracker-timeout=10, dht-entry-point=dht.transmissionbt.com:6881, enable-dht=true, enable-dht6=true, enable-peer-exchange=true, peer-agent=Transmission/3.00, peer-id-prefix=-TR3000-

Caution

check-certificate=false disables TLS certificate verification for the engine. This is a deliberate convenience trade-off (many download mirrors have broken certs) but means HTTPS downloads are not protected against MITM at the transport layer. Reviewers in high-security environments should be aware of this and can override it via system.json (check-integrity/checksum are available for content verification).

5.2 Notable system option groups (from systemKeys)

The ~110 recognized keys group into:

  • Connections & speed: max-concurrent-downloads, max-connection-per-server, split, min-split-size, max-download-limit, max-overall-download-limit, max-overall-upload-limit, max-upload-limit, lowest-speed-limit.
  • Retry & timeout: max-tries, retry-wait, connect-timeout, timeout, max-file-not-found, max-resume-failure-tries.
  • HTTP/FTP: header, referer, user-agent, http-user/http-passwd, http-proxy*, https-proxy*, ftp-user/ftp-passwd, ftp-pasv, ftp-type, http-accept-gzip, http-no-cache, conditional-get, content-disposition-default-utf8.
  • BitTorrent: bt-tracker, bt-exclude-tracker, bt-max-peers, bt-request-peer-speed-limit, bt-min-crypto-level, bt-require-crypto, bt-force-encryption, bt-seed-unverified, bt-save-metadata, bt-load-saved-metadata, bt-stop-timeout, bt-tracker-interval, enable-peer-exchange, bt-enable-lpd, seed-ratio, seed-time.
  • DHT / ports: listen-port, dht-listen-port, dht-file-path, dht-file-path6.
  • RPC: rpc-listen-port, rpc-secret, rpc-save-upload-metadata.
  • Files: dir, out, allow-overwrite, auto-file-renaming, file-allocation, no-file-allocation-limit, enable-mmap, force-save, remove-control-file, remote-time, index-out, select-file.
  • Metalink: follow-metalink, metalink-*.

For the authoritative semantics of each option, see the aria2 manual — Motrix passes them through verbatim.

5.3 How seeding options are applied

Engine.getStartArgs() contains special logic: if the user enabled keep-seeding or seed-ratio === 0, Motrix forces seed-ratio=0 and deletes seed-time from the args — telling aria2 to seed without a ratio cap or time limit.


6. Security Model

This is the most important section for security reviewers. Motrix predates the modern Electron hardening playbook, and its bundled engine has permissive RPC defaults. None of this is a "bug" in the sense of being accidental — but it defines a real trust boundary.

6.1 Electron process configuration

  • @electron/remote is enabled for every window (Launcher.handleRendererRemote() calls require('@electron/remote/main').enable(window.webContents)), and initialize() runs in src/main/index.js.
  • The renderer uses Node/Electron APIs directly (ipcRenderer, electron-is), indicating nodeIntegration: true and no contextIsolation (the hardened contextBridge pattern is not used).
  • process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' is set in src/main/index.js, suppressing Electron's built-in console warnings about the above.

Warning

This is the opposite of the hardened Electron model (context isolation + sandbox + contextBridge). The mitigating factor is that Motrix loads only local, first-party content — it is not a browser for arbitrary web pages. The practical risk is concentrated in (a) the IPC command surface and (b) the engine RPC, below.

6.2 Engine RPC exposure

The bundled aria2.conf enables the RPC server with rpc-listen-all=true and rpc-allow-origin-all=true, and the default rpc-secret is empty.

  • rpc-listen-all=true means aria2 listens on all network interfaces, not just loopback — even though Motrix itself connects via 127.0.0.1:16800 (ENGINE_RPC_HOST).
  • rpc-allow-origin-all=true disables origin checks on RPC requests.
  • An empty secret means no authentication.

Caution

On a shared or untrusted LAN, any host that can reach TCP 16800 can issue aria2 RPC commands (add downloads, change global options, shut down the engine) without authentication. This is the substance of GitHub issue #1795 ("RPC default listen address should be 127.0.0.1 not 0.0.0.0"). Mitigation: set a non-empty RPC Secret in Preferences ▸ Advanced (system key rpc-secret), and/or firewall port 16800 to loopback. Administrators deploying Motrix to managed fleets should ship a system.json with a strong rpc-secret and consider patching the bundled aria2.conf to rpc-listen-all=false.

6.3 IPC trust boundary

The command/event IPC channels re-emit any string the renderer sends on the main-process EventEmitter (Application.handleIpcMessages()). Registered commands include application:open-external (which calls shell.openExternal). Because the channel is not action-allow-listed, a compromised or malicious renderer context could invoke any registered command — the basis of GitHub #1796.

Important

For a local, first-party-only desktop app this boundary is low-risk in normal use, but it means renderer compromise = main-process command execution. Hardening would require migrating to contextBridge with an explicit, validated action allow-list. This is noted in §12 Potential Improvements.

6.4 Auto-update integrity

UpdateManager uses electron-updater against two publish providers (generic https://dl.motrix.app/releases/ and GitHub). Updates are not auto-downloaded (autoDownload = false) — the user is prompted before download and before install (autoInstallOnAppQuit = false). The Mac App Store build (is.mas()) skips the updater entirely. Proxy settings are honored via updater.netSession.setProxy.

6.5 What does NOT exist (negative knowledge)

To prevent fabrication of imaginary attack/config surfaces, the following are confirmed absent in this codebase:

  • No HTTP API, no webhooks, no telemetry/analytics backend. Motrix makes outbound requests only for tracker lists, update checks, and downloads you initiate.
  • No multi-user / account system, no remote management plane.
  • No system-wide config file (/etc/motrix, registry policy keys) — all config is per-user electron-store JSON.
  • No code-signing/notarization in the committed config: electron-builder.json sets macOS hardenedRuntime: false, notarize: false, and type: "development". Production release signing/notarization is handled by the maintainer's CI environment (@electron/notarize, @electron/osx-sign are present as dev deps), not by the committed config.
  • No Electron fuses (@electron/fuses is not used).

7. Operations

7.1 Filesystem layout (per-user data)

Paths are resolved in src/main/utils/index.js. The base is app.getPath('userData') (or PORTABLE_EXECUTABLE_DIR in portable mode):

File Path helper Purpose
system.json getConfigBasePath() aria2 engine config store
user.json getConfigBasePath() App preference store
download.session getSessionPath() aria2 saved session (tasks restored on launch)
engine.pid getEnginePidPath() PID of the running aria2c process
dht.dat / dht6.dat getDhtPath() DHT routing tables (IPv4/IPv6)
main.log electron-log App log (portable: alongside executable)

The bundled engine lives at getEnginePath() → in production resolve(app.getAppPath(), '../engine'); the binary name is platform-specific (aria2c / aria2c.exe) and the config is aria2.conf in the same folder.

Note

Typical userData locations: Windows %APPDATA%\Motrix, macOS ~/Library/Application Support/Motrix, Linux ~/.config/Motrix. The exact path is whatever Electron returns for the app.motrix.native app ID.

7.2 Engine lifecycle

Operation Mechanism
Start Engine.start() → spawn(aria2c, args) with --conf-path, --save-session, and --input-file (if a session exists), plus transformConfig(systemConfig) flags. Writes engine.pid.
Stop Application.stopEngine() → EngineClient.shutdown({force:true}) (RPC forceShutdown), then Engine.stop() kills the child and clears the PID file.
Restart Engine.restart() = stop + start. Triggered by needRestartKeys changes.
Reset session application:reset-session → stop engine, clear recent docs, delete download.session, restart after 3s.
Factory reset application:factory-reset → detach config listeners, configManager.reset() (clears both stores), relaunch.

7.3 Logs

electron-log writes to main.log. The file level is set from the log-level user pref (default warn; valid levels: error, warn, info, verbose, debug, silly) and updated live when the pref changes. In production the base level is info; in dev it's silly. The log path is surfaced read-only in Preferences ▸ Developer ▸ App log path.

7.4 Process supervision & power

  • Single instance: Launcher.makeSingleInstance() uses app.requestSingleInstanceLock() (skipped for the Mac App Store sandbox). A second launch forwards its argv (URL/file) to the running instance via second-instance.
  • Energy: EnergyManager starts a powerSaveBlocker('prevent-app-suspension') while any download is active and stops it when idle, so downloads aren't suspended by sleep.
  • Crash safety: aria2 saves its session every 10s (save-session-interval=10) and control files every 10s (auto-save-interval=10), so an abrupt exit loses at most ~10s of progress metadata.

7.5 Port mapping (UPnP/NAT-PMP)

UPnPManager (via @motrix/nat-api, autoUpdate:true) maps the BT listen-port and dht-listen-port when enable-upnp is true. Port changes re-map automatically; disabling UPnP unmaps and closes the client. Mapping uses Promise.allSettled so a single failure (e.g. no UPnP-capable router) doesn't block startup.

7.6 Tracker auto-sync

When auto-sync-tracker is on and the interval (AUTO_SYNC_TRACKER_INTERVAL = 12 hours) has elapsed since last-sync-tracker-time, Motrix fetches the configured tracker-source lists, converts them to a comma string, reduces them to fit MAX_BT_TRACKER_LENGTH (6144 — guards against Windows spawn ENAMETOOLONG), and writes the result to bt-tracker.


8. Build & Distribution

8.1 Build scripts (package.json)

Script Action
yarn dev node .electron-vue/dev-runner.js — dev with HMR
yarn build node .electron-vue/build.js && electron-builder — full release build
yarn build:applesilicon Build arm64 mac
yarn build:dir Unpacked dir build (no installer)
yarn pack / pack:main / pack:renderer Webpack production bundles only
yarn lint / lint:fix ESLint over .js/.vue in src

asar: true packs the app; the engine binaries are shipped as extraResources (./extra/{platform}/${arch}/ → app root), keeping the native aria2c outside the asar so it can be executed.

8.2 Distribution matrix (electron-builder.json)

Platform Targets Arches
macOS dmg, zip x64, arm64, universal
Windows nsis (installer), appx, zip, portable x64, ia32
Linux AppImage, deb, rpm, snap x64, arm64, armv7l (rpm/snap x64 only)
  • NSIS: non-one-click, allows changing install dir; artifact Motrix-Setup-${version}.exe.
  • AppX: identity 59744DrrOot.Motrix, publisher Dr_rOot — for the Microsoft Store.
  • DMG: custom window layout with an Applications-folder drop target.
  • macOS entries set darkModeSupport: true, category: public.app-category.utilities.

8.3 Protocols & file associations

Registered in electron-builder.json and ProtocolManager:

Protocol Schemes Default
Motrix Protocol mo, motrix Always (internal)
Magnet Protocol magnet On
Thunder Protocol thunder Off

File association: .torrent (MIME application/x-bittorrent, role Viewer). Linux additionally declares mimeTypes for bittorrent and x-scheme-handler/magnet.

The mo:///motrix:// scheme routes through ProtocolManager.handleMoProtocol() → src/main/configs/protocol.js map → an application command with query-string args. Resource schemes (http/https/ftp/magnet/thunder) route to application:new-task.

8.4 Publish & update channels

"publish": [
  { "provider": "generic", "url": "https://dl.motrix.app/releases/" },
  { "provider": "github" }
]

update-channel defaults to latest. In dev, UpdateManager points updateConfigPath at the repo's app-update.yml.

8.5 Build hooks

afterPack: ./build/afterPackHook.js and afterSign: ./build/afterSignHook.js run during packaging (e.g. for signing/notarization steps). These live in build/ (not in the analyzed source set) and should be reviewed before trusting a release pipeline.


9. Module Reference (Internals)

9.1 Renderer aria2 client — src/renderer/api/Api.js

The renderer's Api class is the primary engine driver. On init it loads config via the get-app-config IPC invoke, camelCases the keys, then opens an Aria2 JSON-RPC client to 127.0.0.1:<rpcListenPort>. Selected methods (each maps to an aria2 RPC method):

Method aria2 RPC Purpose
addUri(uris, outs, options) aria2.addUri (multicall) Add HTTP/FTP/magnet tasks
addTorrent(torrent, options) aria2.addTorrent Add a .torrent
addMetalink(metalink, options) aria2.addMetalink Add a metalink
fetchDownloadingTaskList tellActive + tellWaiting (multicall, merged) Downloading list
fetchStoppedTaskList tellStopped History
fetchTaskItemWithPeers tellStatus + getPeers Task detail + peers
pause/resume/remove (+ force*, *All) pause/unpause/remove/… Task control
changeOption / changeGlobalOption changeOption/changeGlobalOption Per-task / global options
batch* system.multicall Bulk operations over many GIDs
saveSession / purgeTaskRecord / removeTaskRecord saveSession/purgeDownloadResult/removeDownloadResult Session & history
getGlobalStat / getVersion getGlobalStat/getVersion Stats & engine version

Options are normalized for the engine via formatOptionsForEngine (kebab-case keys, drop empties) and compactUndefined.

9.2 Vuex store — src/renderer/store/modules/

  • task.js — the task lifecycle. State: taskList, currentList (active/waiting/stopped), selectedGidList, currentTaskItem, seedingList, detail visibility. Actions wrap the Api: addUri/addTorrent/addMetalink, fetchList, pause/resume/toggleTask, removeTask/removeTaskRecord, batch*, stopSeeding (sets seedTime:0). Every mutating action re-fetches the list and calls saveSession. BT tasks are force-paused (checkTaskIsBT).
  • preference.js — config state + save/fetchPreference, history/favorite directory management (fixed-length, max 5), tracker fetch, theme/locale updates.
  • app.js — global UI state (add-task dialog, engine mode, menu states).

9.3 Shared aria2 RPC lib — src/shared/aria2/lib/

A small, self-contained JSON-RPC client: Aria2.js (connection, call, multicall, open/close over websocket via ws), JSONRPCClient.js, JSONRPCError.js, Deferred.js, promiseEvent.js. This is what both EngineClient (main) and Api (renderer) build on.

9.4 Shared utilities — src/shared/utils/

separateConfig (split into user/system/others by key list), formatOptionsForEngine, transformConfig (config → CLI args), changeKeysToCamelCase/changeKeysToKebabCase, mergeTaskResult, detectResource, checkTaskIsBT, plus tracker.js (fetch/convert/reduce tracker lists), rename.js, curl.js, tray.js.

9.5 Localization — src/shared/locales/

25 locale folders, each with about/app/edit/help/index/menu/preferences/subnav/task/window.js. RTL languages (ar, fa, he, ku, pa, ps, sd, ur, yi) are handled via SUPPORT_RTL_LOCALES and getLangDirection. Menu accelerators are derived from src/shared/keymap.json and translated per-locale in src/main/utils/menu.js.


10. Code Quality & Patterns

Strengths

  • Clean manager decomposition. The main process is a set of single-responsibility managers wired by Application, each owning one concern (config, engine, tray, updates, UPnP). Easy to reason about.
  • Reactive config. electron-store's onDidChange/onDidAnyChange drives live behavior (proxy, theme, locale, ports, tray) without restarts where possible; needRestartKeys cleanly delineates what does need a restart.
  • Defensive engine handling. Missing-binary throws a localized error; Promise.allSettled everywhere port mapping or shutdown could partially fail; PID file cleanup on close; tracker-string reduction to dodge Windows arg-length limits.
  • Separation of config domains. The user/system/others split with an explicit allow-list keeps unknown keys out of the engine.

Conventions

  • ES modules, Babel-transpiled. ESLint with @vue/eslint-config-standard.
  • Path aliases @ (renderer), @shared (shared) configured in webpack/jsconfig.
  • Heavy use of constants (src/shared/constants.js) rather than magic values.

Weak spots / risks (see also §12)

  • Old Electron security posture (§6).
  • The IPC channel is un-allow-listed (§6.3).
  • A couple of @TODOs in the renderer (addMetalink is wired in the store/API but not exposed in the AddTask UI; loadConfigFromLocalStorage is a stub for non-renderer contexts).

11. Troubleshooting (Operational)

Engine won't start / "engine bin is not exist"

Engine.getEngineBinPath() throws if the bundled aria2c is missing. Verify the engine/ folder sits next to the app (resolve(app.getAppPath(), '../engine')) and that AV/endpoint-protection isn't quarantining aria2c. On Linux, run once with sudo so the session file under /var/cache can be created.

Downloads don't restore after restart

Check that download.session exists in userData and that Automatically resume all unfinished tasks is enabled (it triggers unpauseAll on launch). A corrupt session can be cleared via Reset download session.

RPC port conflict

If 16800 is taken, change RPC Listen Port (a needRestartKeys key — Motrix restarts the engine). Likewise 21301/26701 for BT/DHT.

Memory growth over long sessions

A suspected leak is reported (GitHub #1798). Mitigate by periodically restarting the app for very long-lived sessions; keep to the latest release.

Window won't show / app hidden in tray mode

In Hide Tray Application mode there's no tray indicator; relaunch the app (single-instance lock will surface the existing window) or quit via Ctrl+Q. macOS dock visibility follows run-mode and full-screen transitions (DockManager).

Build: native module ABI mismatch

If the app crashes after a dependency change, re-run yarn so postinstall's electron-builder install-app-deps rebuilds native modules (@motrix/nat-api) against Electron's ABI.


12. Potential Improvements

These are derived from the code and the issue tracker — candidate work for a maintainer or fork (e.g. the community "Motrix Next" rewrite noted in #1805).

  1. Harden the Electron security model. Migrate to contextIsolation: true + sandbox: true + contextBridge with a validated action allow-list; drop @electron/remote. Directly addresses #1796.
  2. Lock down the engine RPC. Patch the bundled aria2.conf to rpc-listen-all=false, generate a random rpc-secret on first run, and bind to 127.0.0.1 only. Addresses #1795.
  3. Adopt Electron fuses (OnlyLoadAppFromAsar, disable RunAsNode/inspect args) at build time.
  4. Modernize the stack. Vue 2.7 and Element UI 2 are EOL; Electron 22 is past end-of-life. A Vue 3 + maintained-component-library + current-Electron migration is the largest single risk reduction.
  5. Expose addMetalink in the UI (already wired in store/API).
  6. Multi-select task list as a first-class feature (#1766) and per-task download-path changes for active tasks (#1794).
  7. Investigate the suspected memory leak (#1798) and the Edge-extension double-download (#1516).
  8. Portable-mode data isolation — ensure all state stays in the portable folder (#1016).

Note

Development has slowed and the maintainer's status is discussed in #1396/#1807. Treat this codebase as stable-but-quiet: dependable for use, but expect to fork for deep changes.


13. Quick Reference

Ports & endpoints

Item Default
RPC host:port 127.0.0.1:16800
BT listen port 21301
DHT listen port 26701
RPC secret (empty — set one)

Key files (per-user data dir)

File Purpose
system.json aria2 engine options
user.json app preferences
download.session restorable task session
engine.pid aria2 process PID
dht.dat / dht6.dat DHT tables
main.log app log

Application commands (selected, IPC command channel)

application:new-task, application:new-bt-task, application:open-file, application:save-preference, application:reset-session, application:factory-reset, application:check-for-updates, application:change-theme, application:change-locale, application:relaunch, application:quit, application:open-external, application:reveal-in-folder, application:setup-protocols-client.

Engine restart-triggering keys

dht-listen-port, hide-app-menu, listen-port, rpc-listen-port, rpc-secret.

Enums

Enum Values
Run mode Standard (1), Tray (2), Hide Tray (3)
Theme auto, light, dark
Task status active, waiting, paused, error, complete, removed, seeding
Proxy scope download, update-app, update-trackers
Add task type uri, torrent

Build commands

yarn            # install + rebuild native deps
yarn dev        # development with HMR
yarn build      # full electron-builder release
yarn lint:fix   # lint and auto-fix