Skip to content

Lua‐Configuration

Trim21 edited this page May 12, 2026 · 1 revision

Lua Configuration Guide for rTorrent

This guide covers writing configuration files for rTorrent using Lua instead of the traditional .rc format.

Table of Contents


Quick Start

  1. Create your Lua config file (e.g. ~/.config/rtorrent/rtorrent.rc.lua).
  2. In your rtorrent.rc, add a single line to load it:
lua.execute = (cat, (system.env, HOME), "/.config/rtorrent/rtorrent.rc.lua")
  1. At the top of your .rc.lua file:
local rtorrent = require('rtorrent')
rc = rtorrent.autocall
  1. Use rc to call any rTorrent command:
rc.network.port_range = '50000-50000'
rc.session.path = '/home/user/.rtorrent/.session/'
rc.directory.default = '/home/user/rtorrent/download/'

Core Concepts

1. rtorrent.autocall

rtorrent.autocall is a proxy object that uses a metatable chain to convert dotted property access into rtorrent.call() invocations. Each . access appends to an internal name stack. When the result is called as a function (with ()), it executes the accumulated command name.

How it works internally:

-- This expression:
rc.system.hostname()

-- Is equivalent to:
rtorrent.call("system.hostname", "")

The metatable chain:

  • __index: Each .key access pushes key onto an internal __namestack table and returns a new proxy.
  • __call: When invoked with (), joins the namestack with ., gets the __target (defaults to ""), and calls rtorrent.call(name, target, ...).
  • __newindex: Assignment like rc.foo = value is sugar for rc.foo.set(value).

You can store sub-chains as aliases:

local sys = rc.system
sys.hostname()   -- same as rc.system.hostname()
sys.pid()        -- same as rc.system.pid()

local net = rc.network
net.port_range = '6881-6999'

2. rtorrent.call()

The low-level function that all autocall usage resolves to. Its signature is:

rtorrent.call(method, target, ...args)
  • method (string): The rTorrent command name, e.g. "d.name", "system.hostname".
  • target (string): The target for the command.
    • For d.* commands: the download info hash (hex string).
    • For p.*/f.*/t.* commands: used internally by multicall.
    • For global commands: pass "" (empty string).
  • ...args: Additional arguments depending on the command.

Example:

-- Get the system hostname (global command, target = "")
local name = rtorrent.call("system.hostname", "")

-- Get a download's name (requires info hash)
local dl_name = rtorrent.call("d.name", "ABCDEF1234567890...")

-- Set a value
rtorrent.call("throttle.global_up.max_rate.set", "", 102400)

3. The rc Shorthand

By convention, rc is assigned as an alias for rtorrent.autocall:

rc = rtorrent.autocall

This lets you write:

rc.session.path('/tmp/session')
-- instead of
rtorrent.autocall.session.path('/tmp/session')

4. Assignment Syntax Sugar

When you assign to an autocall property, it automatically calls .set():

rc.network.port_range = '50000-50000'
-- Is exactly equivalent to:
rc.network.port_range.set('50000-50000')
-- Which is:
rtorrent.call("network.port_range.set", "", '50000-50000')

This works for all variable types:

rc.throttle.max_peers.normal = 60      -- integer
rc.protocol.pex = false                 -- boolean
rc.directory.default = '/data/'         -- string
rc.pieces.memory.max = '1800M'         -- string with unit

5. Targeting Downloads

For commands that require a download target (all d.* commands), use rtorrent.Target(hash):

local dl = rtorrent.Target("ABCDEF1234567890ABCDEF1234567890ABCDEF12")

-- Query download properties:
dl.d.name()              -- get download name
dl.d.up.rate()           -- get upload rate
dl.d.is_active()         -- check if active

-- Set download properties:
dl.d.priority.set(3)     -- set priority to high
dl.d.directory.set('/new/path/')

-- Iterate files:
local files = dl.f.multicall('', 'f.path=', 'f.size_bytes=')

-- Iterate peers:
local peers = dl.p.multicall('p.address=', 'p.up_rate=')

-- Iterate trackers:
local trackers = dl.t.multicall('t.url=', 't.is_enabled=')

Convenience aliases are also available:

rtorrent.d(hash)         -- equivalent to rtorrent.Target(hash, "d")
rtorrent.f(hash)         -- equivalent to rtorrent.Target(hash, "f")
rtorrent.p(hash)         -- equivalent to rtorrent.Target(hash, "p")
rtorrent.t(hash)         -- equivalent to rtorrent.Target(hash, "t")
rtorrent.load(hash)      -- equivalent to rtorrent.Target(hash, "load")

So you can write:

local d = rtorrent.d("ABCDEF1234567890...")
d.name()           -- rtorrent.call("d.name", "ABCDEF1234567890...")
d.up.rate()        -- rtorrent.call("d.up.rate", "ABCDEF1234567890...")

Using Target with multicall:

local dl = rtorrent.Target("INFO_HASH_HERE")

-- f.multicall(filter, *cmds) — filter is a regex, "" means all files
local files = dl.f.multicall('', 'f.path=', 'f.size_bytes=', 'f.completed_chunks=')
for _, row in ipairs(files) do
    local path, size, completed = row[1], row[2], row[3]
    print(path .. ': ' .. size .. ' bytes, ' .. completed .. ' chunks done')
end

-- p.multicall(*cmds)
local peers = dl.p.multicall('p.address=', 'p.client_version=', 'p.up_rate=')
for _, row in ipairs(peers) do
    local addr, client, rate = row[1], row[2], row[3]
    print(addr .. ' (' .. client .. ') up: ' .. rate .. ' B/s')
end

-- t.multicall(*cmds)
local trackers = dl.t.multicall('t.url=', 't.is_enabled=')
for _, row in ipairs(trackers) do
    local url, enabled = row[1], row[2]
    print(url .. ' enabled=' .. tostring(enabled))
end

6. Registering Lua Functions as rTorrent Commands

rtorrent.insert_lua_method(name, func_name, method_args) registers a global Lua function as an rTorrent command that can be used in schedules, event handlers, etc.

Parameters:

Parameter Type Description
name string rTorrent command slot name, e.g. "d.watch_handler"
func_name string Name of a global Lua function
method_args number | string[] | number[] How to pass rTorrent arguments to the Lua function

method_args formats:

  1. Non-negative integer — pass the first N event arguments:

    -- Register handler that receives 1 argument (the torrent file path)
    rtorrent.insert_lua_method('d.watch_handler', 'my_watch_handler', 1)
    
    -- The rTorrent command becomes:
    -- d.watch_handler=$argument.0
    
    -- Your Lua function receives (target, arg0):
    function my_watch_handler(target, filepath)
        print("New torrent: " .. filepath)
    end
  2. Table of strings — explicit rTorrent argument expressions:

    rtorrent.insert_lua_method('d.on_complete', 'handle_complete', {
        '$d.hash=', '$d.name='
    })
    
    -- Your Lua function receives (target, hash, name):
    function handle_complete(target, hash, name)
        print("Completed: " .. name .. " (" .. hash .. ")")
    end
  3. Table of integers — argument indexes (converted to $argument.N):

    rtorrent.insert_lua_method('d.reorder', 'reorder_args', { 1, 0 })
    
    -- The rTorrent command becomes:
    -- d.reorder=$argument.1,$argument.0
    -- i.e. arguments are passed in reversed order to your Lua function

Note: The first argument to your Lua function is always target (may be an empty string for global commands).


Complete Example Config

--[[
  A minimal rTorrent Lua configuration.

  Usage:
    1. Copy this file to ~/.config/rtorrent/rtorrent.rc.lua
    2. In rtorrent.rc, add:
       lua.execute = (cat, (system.env, HOME), "/.config/rtorrent/rtorrent.rc.lua")
]]--

--[[ Bootstrap ]]--

local rtorrent = require('rtorrent')
rc = rtorrent.autocall

--[[ Helper functions ]]--

local function makeArgs(cmdline)
   return 'sh', '-c', "'"..table.concat(cmdline, "' '").."'"
end

--[[ Instance layout (base paths) ]]--

local home = os.getenv('HOME')

local cfg = {}
cfg.basedir = home..'/rtorrent/'
cfg.download = cfg.basedir..'download/'
cfg.logs = cfg.basedir..'log/'
cfg.logfile = cfg.logs..'rtorrent-'..rc.system.time()..'.log'
cfg.session = cfg.basedir..'.session/'
cfg.watch = cfg.basedir..'watch/'

-- Register paths as private constant rTorrent methods
rc.method.insert('cfg.download', 'private|const|string', cfg.download)
rc.method.insert('cfg.logs',     'private|const|string', cfg.logs)
rc.method.insert('cfg.logfile',  'private|const|string', cfg.logfile)
rc.method.insert('cfg.session',  'private|const|string', cfg.session)
rc.method.insert('cfg.watch',    'private|const|string', cfg.watch)

-- Create instance directories
rc.execute.throw(
   makeArgs({'mkdir', '-p',
             cfg.download,
             cfg.logs,
             cfg.session,
             cfg.watch..'/load',
             cfg.watch..'/start'}))

--[[ Network settings ]]--

rc.network.port_range = '50000-50000'
rc.network.port_random = false

-- Tracker-less torrent and UDP tracker support
rc.dht.mode = 'disable'
rc.protocol.pex = false
rc.trackers.use_udp = false

--[[ Peer settings ]]--

rc.throttle.max_uploads = 100
rc.throttle.max_uploads.global = 250

rc.throttle.min_peers.normal = 20
rc.throttle.max_peers.normal = 60
rc.throttle.min_peers.seed = 30
rc.throttle.max_peers.seed = 80
rc.trackers.numwant = 80

rc.protocol.encryption.set('allow_incoming', 'try_outgoing', 'enable_retry')

--[[ Resource limits ]]--

rc.network.max_open_files = 600
rc.network.max_open_sockets = 300
rc.pieces.memory.max = '1800M'
rc.network.xmlrpc.size_limit = '4M'

--[[ Operational settings ]]--

rc.session.path = cfg.session
rc.directory.default = cfg.download
rc.log.execute(cfg.logs.."execute.log")
rc.execute.nothrow(
   "sh", "-c", table.concat(
      {"echo >", rc.session.path(), "rtorrent.pid", " ", rc.system.pid()
}))
rc.encoding.add('utf8')
rc.system.umask = 0027
rc.system.cwd = rc.directory.default()
rc.network.http.dns_cache_timeout = 25
rc.schedule2('monitor_diskspace', '15', '60', 'close_low_diskspace=1000M')

--[[ Custom methods ]]--

rc.method.insert('system.startup_time', 'value|const', rc.system.time())
rc.method.insert('d.data_path', 'simple',
	[[if=(d.is_multi_file),
	 (cat, (d.directory), /),
	 (cat, (d.directory), /, (d.name))]])
rc.method.insert('d.session_file', 'simple', 'cat=(session.path), (d.hash), .torrent')

--[[ Watch directories ]]--

rc.schedule2('watch_start', '10', '10',
   'load.start_verbose=(cat, (cfg.watch), "start/*.torrent")')
rc.schedule2('watch_load', '11', '10',
   'load.verbose=(cat, (cfg.watch), "load/*.torrent")')

--[[ Logging ]]--

rc.print('Logging to '..cfg.logfile)
rc.log.open_file('log', cfg.logfile)
rc.log.add_output('info', 'log')

--[[ SCGI (XMLRPC/JSONRPC) endpoint ]]--

-- Unix socket:
-- rc.network.scgi.open_local(cfg.session..'rtorrent.sock')
-- rc.execute.nothrow('chmod', '770', cfg.session..'rtorrent.sock')

-- TCP port:
-- rc.network.scgi.open_port('127.0.0.1:5000')

--[[ Register a Lua function as an rTorrent command ]]--

local function on_torrent_loaded(target, filepath)
   -- target is the download hash, filepath is $argument.0
   print("Loaded torrent from: " .. filepath)
end

rtorrent.insert_lua_method('event.torrent_loaded', 'on_torrent_loaded', 1)

-- Use it in a schedule or view event:
-- rc.view.event_added('started', 'event.torrent_loaded=$d.tied_to_file=')

API Reference

rtorrent.autocall

Proxy object. Property access builds a dotted command name. Calling the result executes it.

rc = rtorrent.autocall

rc.system.hostname()                    -- call("system.hostname", "")
rc.throttle.max_peers.normal = 60      -- call("throttle.max_peers.normal.set", "", 60)
rc.d.name.set(hash, "New Name")        -- explicit target via .set() with extra arg

rtorrent.call(method, target, ...args)

Low-level call to any rTorrent command.

rtorrent.call("system.hostname", "")                    -- -> "myhost"
rtorrent.call("d.name", "ABCD...EF")                    -- -> "my_torrent.torrent"
rtorrent.call("d.priority.set", "ABCD...EF", 3)         -- -> nil
rtorrent.call("throttle.global_up.max_rate.set", "", 0) -- -> nil (unlimited)

rtorrent.Target(hash, prefix)

Create a target-bound proxy for a specific download.

local dl = rtorrent.Target("ABCDEF1234567890...")
dl.d.name()                  -- call("d.name", "ABCDEF1234567890...")
dl.d.up.rate()               -- call("d.up.rate", "ABCDEF1234567890...")
dl.d.priority.set(3)         -- call("d.priority.set", "ABCDEF1234567890...", 3)

-- With custom prefix:
local custom = rtorrent.Target("ABCDEF1234567890...", "d")
custom.name()                -- call("d.name", "ABCDEF1234567890...")

rtorrent.d(hash), rtorrent.f(hash), rtorrent.p(hash), rtorrent.t(hash)

Convenience constructors for prefix-targeted proxies.

local d = rtorrent.d("ABCDEF1234567890...")
d.name()                     -- call("d.name", "ABCDEF1234567890...")
d.is_active()                -- call("d.is_active", "ABCDEF1234567890...")

rtorrent.insert_lua_method(name, func_name, method_args)

Register a global Lua function as an rTorrent command.

function my_handler(target, arg0, arg1)
   -- do something
end

-- Pass 2 event arguments:
rtorrent.insert_lua_method('my.command', 'my_handler', 2)

-- Pass specific arguments:
rtorrent.insert_lua_method('my.other', 'my_handler', { '$d.hash=', '$d.name=' })

method.insert via autocall

Insert rTorrent methods (non-Lua) directly:

-- Simple command method:
rc.method.insert('my.greeting', 'simple', 'cat=Hello, World!')

-- Value with initial value:
rc.method.insert('my.counter', 'value', 0)

-- String variable:
rc.method.insert('my.label', 'string', 'default')

-- Constant string (read-only):
rc.method.insert('my.info', 'private|const|string', 'v1.0')

-- Multi-method (key-value pairs):
rc.method.insert('my.map', 'multi')
rc.method.set_key('my.map', 'key1', 'value1')
rc.method.set_key('my.map', 'key2', 'value2')

Clone this wiki locally