kahva

package module
v0.0.0-...-87ab93f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 26, 2024 License: MIT Imports: 12 Imported by: 0

README

kahva

XML-RPC/JSON API and web client for rTorrent.

The backend is used to deserialize XML-RPC requests from rTorrent to more browser friendly JSON presentation. It also serves the optional Vue frontend, a dedicated HTTP server is not required.


Features

  • List view torrents
  • List torrent files, trackers, peers
  • Load torrent
  • Manage torrent state
  • Manage global throttle
  • Manage torrent labels
  • Manage rTorrent settings

Screenshots

Torrent view Torrent expander, sticky

Configuration

Environment variables
Backend
  • SERVER_ADDRESS HTTP server bind address, it is 0.0.0.0:8080 by default.
  • XMLRPC_URL Remote XML-RPC server URL, this will be the URL exposed by your nginx (or similar) web server (e.g. https://yourdomain.tld/rpc)
  • XMLRPC_USERNAME Optional basic authentication username
  • XMLRPC_PASSWORD Optional basic authentication password
  • CORS_ORIGIN CORS origin if the frontend runs on a different path
  • CORS_AGE CORS age if the frontend runs on a different path
Frontend
  • VITE_BACKEND_BASE_URL backend address. It will be http://localhost:8080 most likely and NOT set by default. This variable is build time only.
Remote server setup
rTorrent

rTorrent should be configured to expose the XML-RPC interface through a UNIX socket.This can be achieved by adding the following lines to your .rtorrent.rc file. A regular SCGI socket should never be used unless you are for certain that it cannot be accessed from outside your network.

...
scgi_local = /path/to/some/directory/xmlrpc.socket
schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",/path/to/some/directory/xmlrpc.socket"
...

Make sure that the socket is writable by the user that runs nginx (e.g. www-data)

If you have an excessive amount of metadata you might need to increase the default XML-RPC limit. This is almost never required.

network.xmlrpc.size_limit.set = 10M
nginx

Create a nginx virtual host that serves the XML-RPC socket. Basic authentication is optional but recommended (read: a must) if you are accessing the server remotely. Note that the XML-RPC interface can be used to execute shell commands remotely.

You can use the snippet below as an example.

server {
        root /usr/share/nginx/html;
        index index.html;
        server_name yourdomain.tld;

        server_tokens off;
        autoindex off;
        auth_basic_user_file /path/to/some/directory/.htpasswd;
        auth_basic "super secret";

        location /rpc {
                include    scgi_params;
                scgi_pass  unix:/path/to/some/directory/xmlrpc.socket;
        }
}

A configuration like this will serve the socket at http://yourdomain.tld/rpc.

Building

Easiest way to run the application is with Docker. The server will bind to 0.0.0.0 and use port 8080 by default.

Build and run the image using docker compose up --build. Remember to change the environment variables to match your server configuration.

Alternatively the frontend and backend can be compiled separately.

Backend

Install the Go compiler and run go build -v -o ./kahva ./cmd. This should result in a single kahva binary.

Frontend

Clone the frontend repository

Install Node and the required dependencies. Run VITE_BACKEND_BASE_URL=http://localhost:8080 npm run build. This should result in a dist/ directory.

Finally move the dist directory on the same level as the backend executable and rename the folder to www/.

The directory structure should look rougly like this:

./kahva
./www/
     /index.html
     /assets/
     /assets/...

Run the binary with SERVER_ADDRESS=0.0.0.0:8080 OTHER_ENV_VARAIBLES=... ./kahva

Running

The backend can be used as a standalone application to manage rTorrent. Some examples below.

List unregisted torrents
curl localhost:8080/api/view/main | jq -r '.torrents[] | select(.message | ascii_downcase | contains("unregistered torrent")) | .hash'
Routes

All API routes are prefixed with /api

List all torrents in view (default view is main)

GET /api/view/{view}

Show system details (global throttle/rate, versions etc.)

GET /api/system

Load torrent

GET /api/load

the form body should contain a file key which holds the file.

List files/peers/trackers

GET /api/torrent/{hash}/{files,trackers,peers}

Set torrent state or force hash re-check

GET /api/torrent/{hash}/{start,resume,stop,pause,hash,erase}

Set torrent priority

POST /api/torrent/{hash}/priority

the JSON body should contain a key priority which is an integer between 0 and 3

Set global throttle

POST /api/throttle

the JSON body should contain a key type which is up or down and key kilobytes as an integer which represents the throttle limit.

Default fields

The backend implements a subset of fields by default. In order to add more fields add them to the correct struct in rtorrent.go. The field should contain the corresponding tag for deserialization.

Problems?

Check the backend stdout for clues and always verify that your environment variables are correct and point to the right place.

If the server is reporting deserialization issues check the nginx error log and enable XML-RPC logging in .rtorrent.rc by adding the line log.xmlrpc = "/path/to/somewhere/xmlrpc.log".

If you are still experiencing issues and you are absolutely sure that the problem is not with your server and/or configuration, create an issue in this repository.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CORSMiddleware

func CORSMiddleware(next http.Handler) http.Handler

func LoadHandler

func LoadHandler(rt *Rtorrent) http.HandlerFunc

func SystemHandler

func SystemHandler(rt *Rtorrent) http.HandlerFunc

func ThrottleHandler

func ThrottleHandler(rt *Rtorrent) http.HandlerFunc

func TorrentHandler

func TorrentHandler(rt *Rtorrent) http.HandlerFunc

func ViewHandler

func ViewHandler(rt *Rtorrent) http.HandlerFunc

Types

type Config

type Config struct {
	URL       string
	Transport http.RoundTripper
}

type ErrorResponse

type ErrorResponse struct {
	Status  string `json:"status"`
	Message string `json:"message"`
}

type File

type File struct {
	Path            string `rt:"f.path=" json:"path"`
	Size            int64  `rt:"f.size_bytes=" json:"size"`
	SizeChunks      int64  `rt:"f.size_chunks=" json:"size_chunks"`
	CompletedChunks int64  `rt:"f.completed_chunks=" json:"completed_chunks"`
	FrozenPath      string `rt:"f.frozen_path=" json:"frozen_path"`
	Priority        int64  `rt:"f.priority=" json:"priority"`
	IsCreated       int64  `rt:"f.is_created=" json:"is_created"`
	IsOpen          int64  `rt:"f.is_open=" json:"is_open"`
}

type FilesResponse

type FilesResponse struct {
	Status string `json:"status"`
	Files  []File `json:"files"`
}

type Peer

type Peer struct {
	ID               string `rt:"p.id=" json:"id"`
	Address          string `rt:"p.address=" json:"address"`
	Port             int64  `rt:"p.port=" json:"port"`
	Banned           int64  `rt:"p.banned=" json:"banned"`
	ClientVersion    string `rt:"p.client_version=" json:"client_version"`
	CompletedPercent int64  `rt:"p.completed_percent=" json:"completed_percent"`
	IsEncrypted      int64  `rt:"p.is_encrypted=" json:"is_encrypted"`
	IsIncoming       int64  `rt:"p.is_incoming=" json:"is_incoming"`
	IsObfuscated     int64  `rt:"p.is_obfuscated=" json:"is_obfuscated"`
	DownloadRate     int64  `rt:"p.peer_rate=" json:"down_rate"`
	DownloadTotal    int64  `rt:"p.peer_total=" json:"down_total"`
	UploadRate       int64  `rt:"p.up_rate=" json:"up_rate"`
	UploadTotal      int64  `rt:"p.up_total=" json:"up_total"`
}

type PeersResponse

type PeersResponse struct {
	Status string `json:"status"`
	Peers  []Peer `json:"peers"`
}

type Response

type Response struct {
	Status  string `json:"status"`
	Message string `json:"message"`
}

type Rtorrent

type Rtorrent struct {
	// contains filtered or unexported fields
}

func NewRtorrent

func NewRtorrent(config Config) (*Rtorrent, error)

Creates a new instance of Rtorrent client

func (*Rtorrent) CheckHash

func (rt *Rtorrent) CheckHash(hash string) error

Pause torrent with the specified hash

func (*Rtorrent) Close

func (rt *Rtorrent) Close() error

Closes the underlying XMLRPC client.

func (*Rtorrent) DMulticall

func (rt *Rtorrent) DMulticall(target string, args interface{}) ([]Torrent, error)

View multicall.

func (*Rtorrent) Erase

func (rt *Rtorrent) Erase(hash string) error

Erase torrent with the specified hash

func (*Rtorrent) FMulticall

func (rt *Rtorrent) FMulticall(args interface{}) ([]File, error)

File multicall.

func (*Rtorrent) GlobalThrottleDown

func (rt *Rtorrent) GlobalThrottleDown(kilobytes int) error

Set global down throttle.

func (*Rtorrent) GlobalThrottleUp

func (rt *Rtorrent) GlobalThrottleUp(kilobytes int) error

Set global up throttle.

func (*Rtorrent) ListMethods

func (rt *Rtorrent) ListMethods() ([]string, error)

Lists available XMLRPC methods

func (*Rtorrent) LoadRawStart

func (rt *Rtorrent) LoadRawStart(file []byte) error

Load and start a torrent

func (*Rtorrent) PMulticall

func (rt *Rtorrent) PMulticall(args interface{}) ([]Peer, error)

Peer multicall.

func (*Rtorrent) Pause

func (rt *Rtorrent) Pause(hash string) error

Pause torrent with the specified hash

func (*Rtorrent) Priority

func (rt *Rtorrent) Priority(hash string, priority int) error

Set torrent priority

func (*Rtorrent) Resume

func (rt *Rtorrent) Resume(hash string) error

Pause torrent with the specified hash

func (*Rtorrent) Start

func (rt *Rtorrent) Start(hash string) error

Start torrent with the specified hash

func (*Rtorrent) Stop

func (rt *Rtorrent) Stop(hash string) error

Stop torrent with the specified hash

func (*Rtorrent) SystemMulticall

func (rt *Rtorrent) SystemMulticall(args interface{}) (System, error)

System multicall.

func (*Rtorrent) TMulticall

func (rt *Rtorrent) TMulticall(args interface{}) ([]Tracker, error)

Torrent multicall.

type System

type System struct {
	APIVersion     string `rt:"system.api_version" json:"api_version"`
	ClientVersion  string `rt:"system.client_version" json:"client_version"`
	LibraryVersion string `rt:"system.library_version" json:"library_version"`

	Hostname string `rt:"system.hostname" json:"hostname"`
	PID      int64  `rt:"system.pid" json:"pid"`
	Time     int64  `rt:"system.time_seconds" json:"time_seconds"`

	ThrottleGlobalDownTotal   int64 `rt:"throttle.global_down.total" json:"throttle_global_down_total"`
	ThrottleGlobalUpTotal     int64 `rt:"throttle.global_up.total" json:"throttle_global_up_total"`
	ThrottleGlobalDownRate    int64 `rt:"throttle.global_down.rate" json:"throttle_global_down_rate"`
	ThrottleGlobalUpRate      int64 `rt:"throttle.global_up.rate" json:"throttle_global_up_rate"`
	ThrottleGlobalDownMaxRate int64 `rt:"throttle.global_down.max_rate" json:"throttle_global_down_max_rate"`
	ThrottleGlobalUpMaxRate   int64 `rt:"throttle.global_up.max_rate" json:"throttle_global_up_max_rate"`
}

type SystemCall

type SystemCall struct {
	MethodName string      `xmlrpc:"methodName" json:"method_name"`
	Params     interface{} `xmlrpc:"params" json:"params"`
}

type SystemResponse

type SystemResponse struct {
	Status string `json:"status"`
	System System `json:"system"`
}

type ThrottleRequest

type ThrottleRequest struct {
	Type      string `json:"type"`
	Kilobytes int    `json:"kilobytes"`
}

type Torrent

type Torrent struct {
	Hash           string `rt:"d.hash=" json:"hash"`
	Name           string `rt:"d.name=" json:"name"`
	SizeBytes      int64  `rt:"d.size_bytes=" json:"size_bytes"`
	CompletedBytes int64  `rt:"d.completed_bytes=" json:"completed_bytes"`
	UploadRate     int64  `rt:"d.up.rate=" json:"upload_rate"`
	UploadTotal    int64  `rt:"d.up.total=" json:"upload_total"`
	DownloadRate   int64  `rt:"d.down.rate=" json:"download_rate"`
	DownloadTotal  int64  `rt:"d.down.total=" json:"download_total"`
	Message        string `rt:"d.message=" json:"message"`
	BaseFilename   string `rt:"d.base_filename=" json:"base_filename"`
	BasePath       string `rt:"d.base_path=" json:"base_path"`
	IsActive       int64  `rt:"d.is_active=" json:"is_active"`
	IsOpen         int64  `rt:"d.is_open=" json:"is_open"`
	IsHashing      int64  `rt:"d.is_hash_checking=" json:"is_hashing"`
	Leechers       int64  `rt:"d.peers_accounted=" json:"leechers"`
	Seeders        int64  `rt:"d.peers_complete=" json:"seeders"`
	State          int64  `rt:"d.state=" json:"state"`
	StateChanged   int64  `rt:"d.state_changed=" json:"state_changed"`
	StateCounter   int64  `rt:"d.state_counter=" json:"state_counter"`
	Priority       int64  `rt:"d.priority=" json:"priority"`
	Custom1        string `rt:"d.custom1=" json:"custom1"`
	Custom2        string `rt:"d.custom2=" json:"custom2"`
	Custom3        string `rt:"d.custom3=" json:"custom3"`
	Custom4        string `rt:"d.custom4=" json:"custom4"`
	Custom5        string `rt:"d.custom5=" json:"custom5"`
}

type TorrentPriorityRequest

type TorrentPriorityRequest struct {
	Priority int `json:"priority"`
}

type Tracker

type Tracker struct {
	ID               string `rt:"t.id=" json:"tracker_id"`
	ActivityTimeLast int64  `rt:"t.activity_time_last=" json:"activity_time_last"`
	ActivityTimeNext int64  `rt:"t.activity_time_next=" json:"activity_time_next"`
	CanScrape        int64  `rt:"t.can_scrape=" json:"can_scrape"`
	IsUsable         int64  `rt:"t.is_usable=" json:"t.is_usable"`
	IsEnabled        int64  `rt:"t.is_enabled=" json:"is_enabled"`
	FailedCounter    int64  `rt:"t.failed_counter=" json:"failed_counter"`
	FailedTimeLast   int64  `rt:"t.failed_time_last=" json:"failed_time_last"`
	FailedTimeNext   int64  `rt:"t.failed_time_next=" json:"failed_time_next"`
	LatestEvent      string `rt:"t.latest_event=" json:"latest_event"`
	IsBusy           int64  `rt:"t.is_busy=" json:"is_busy"`
	IsOpen           int64  `rt:"t.is_open=" json:"is_open"`
	Type             int64  `rt:"t.type=" json:"type"`
	URL              string `rt:"t.url=" json:"url"`
}

type TrackersResponse

type TrackersResponse struct {
	Status   string    `json:"status"`
	Trackers []Tracker `json:"trackers"`
}

type ViewResponse

type ViewResponse struct {
	Status   string    `json:"status"`
	Torrents []Torrent `json:"torrents"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL