Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ RSpec/IndexedLet:
- '^error\d+$' # Allow error1, error2, etc.
- '^suggestion\d+$' # Allow suggestion1, suggestion2, etc.

RSpec/SpecFilePathFormat:
CustomTransform:
RCon: rcon

Style/HashEachMethods:
Exclude:
- lib/factorix/cli/commands/cache/stat.rb # cache.each is not Hash#each, it's Cache::Base#each
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## [Unreleased]

### Added

- Add `rcon exec` command to execute a Factorio console command via RCon (#87)
- Add `rcon eval` command to evaluate a Lua script in a running Factorio server via RCon (#87)

## [0.12.0] - 2026-04-21

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Factorix simplifies Factorio MOD management by providing:
- **Blueprint Conversion**: Decode/encode Factorio blueprint strings to/from JSON
- **MOD Portal Integration**: Upload new MODs or update existing ones, edit metadata
- **Game Control**: Launch Factorio from the command line
- **RCon**: Execute console commands and Lua scripts on a running Factorio server via RCon
- **Game Download**: Download Factorio game files (alpha, expansion, demo, headless)
- **Cross-platform Support**: Works on Windows, Linux, macOS, and WSL

Expand Down
21 changes: 20 additions & 1 deletion completion/_factorix.bash
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ _factorix() {
local confirmable_opts="-y --yes"

# Top-level commands
local commands="version man launch path download blueprint mod cache completion"
local commands="version man launch path download blueprint mod cache rcon completion"

# blueprint subcommands
local blueprint_commands="decode encode"
Expand All @@ -28,6 +28,10 @@ _factorix() {
# cache subcommands
local cache_commands="stat evict"

# rcon subcommands
local rcon_commands="exec eval"
local rcon_opts="--host --port --password"

# mod image subcommands
local image_commands="list add edit"

Expand Down Expand Up @@ -110,6 +114,21 @@ _factorix() {
fi
return
;;
rcon)
if [[ $cword -eq 2 ]]; then
COMPREPLY=($(compgen -W "$rcon_commands" -- "$cur"))
else
case "${words[2]}" in
exec|eval)
COMPREPLY=($(compgen -W "$global_opts $rcon_opts" -- "$cur"))
;;
*)
COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
;;
esac
fi
return
;;
mod)
if [[ $cword -eq 2 ]]; then
COMPREPLY=($(compgen -W "$mod_commands" -- "$cur"))
Expand Down
33 changes: 24 additions & 9 deletions completion/_factorix.fish
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,16 @@ complete -c factorix -l log-level -d 'Set log level' -xa 'debug info warn error
complete -c factorix -s q -l quiet -d 'Suppress non-essential output'

# Top-level commands
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a version -d 'Display Factorix version'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a man -d 'Display the Factorix manual page'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a launch -d 'Launch Factorio game'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a path -d 'Display Factorio and Factorix paths'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a download -d 'Download Factorio game files'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a blueprint -d 'Blueprint decode/encode commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a mod -d 'MOD management commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a cache -d 'Cache management commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a completion -d 'Generate shell completion script'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a version -d 'Display Factorix version'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a man -d 'Display the Factorix manual page'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a launch -d 'Launch Factorio game'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a path -d 'Display Factorio and Factorix paths'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a download -d 'Download Factorio game files'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a blueprint -d 'Blueprint decode/encode commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a mod -d 'MOD management commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a cache -d 'Cache management commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a rcon -d 'RCon commands'
complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a completion -d 'Generate shell completion script'

# blueprint subcommands
complete -c factorix -n "__factorix_using_command blueprint" -a decode -d 'Decode a Factorio blueprint string to JSON'
Expand Down Expand Up @@ -143,6 +144,20 @@ complete -c factorix -n "__factorix_using_command download" -s c -l channel -d '
complete -c factorix -n "__factorix_using_command download" -s d -l directory -d 'Download directory' -ra '(__fish_complete_directories)'
complete -c factorix -n "__factorix_using_command download" -s o -l output -d 'Output filename' -r

# rcon subcommands
complete -c factorix -n "__factorix_using_command rcon" -a exec -d 'Execute a Factorio console command'
complete -c factorix -n "__factorix_using_command rcon" -a eval -d 'Evaluate a Lua script'

# rcon exec options
complete -c factorix -n "__factorix_using_subcommand rcon exec" -l host -d 'RCon host' -r
complete -c factorix -n "__factorix_using_subcommand rcon exec" -l port -d 'RCon port' -r
complete -c factorix -n "__factorix_using_subcommand rcon exec" -l password -d 'RCon password' -r

# rcon eval options
complete -c factorix -n "__factorix_using_subcommand rcon eval" -l host -d 'RCon host' -r
complete -c factorix -n "__factorix_using_subcommand rcon eval" -l port -d 'RCon port' -r
complete -c factorix -n "__factorix_using_subcommand rcon eval" -l password -d 'RCon password' -r

# cache subcommands
complete -c factorix -n "__factorix_using_command cache" -a stat -d 'Display cache statistics'
complete -c factorix -n "__factorix_using_command cache" -a evict -d 'Evict cache entries'
Expand Down
54 changes: 54 additions & 0 deletions completion/_factorix.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ _factorix() {
'blueprint:Blueprint decode/encode commands'
'mod:MOD management commands'
'cache:Cache management commands'
'rcon:RCon commands'
'completion:Generate shell completion script'
)
_describe -t commands 'factorix command' commands
Expand Down Expand Up @@ -78,6 +79,9 @@ _factorix() {
cache)
_factorix_cache
;;
rcon)
_factorix_rcon
;;
esac
;;
esac
Expand Down Expand Up @@ -461,6 +465,56 @@ _factorix_mod_settings() {
esac
}

_factorix_rcon() {
local context state state_descr line
typeset -A opt_args

local -a global_opts
global_opts=(
'(-c --config-path)'{-c,--config-path}'[Path to configuration file]:config file:_files'
'--log-level[Set log level]:level:(debug info warn error fatal)'
'(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
)

local -a rcon_opts
rcon_opts=(
'--host[RCon host]:host:'
'--port[RCon port]:port:'
'--password[RCon password]:password:'
)

_arguments -C \
'1:subcommand:->subcommand' \
'*::arg:->args'

case $state in
subcommand)
local -a subcommands
subcommands=(
'exec:Execute a Factorio console command'
'eval:Evaluate a Lua script'
)
_describe -t subcommands 'rcon subcommand' subcommands
;;
args)
case $line[1] in
exec)
_arguments \
$global_opts \
$rcon_opts \
'1:console command:'
;;
eval)
_arguments \
$global_opts \
$rcon_opts \
'1:Lua script:'
;;
esac
;;
esac
}

_factorix_cache() {
local context state state_descr line
typeset -A opt_args
Expand Down
52 changes: 52 additions & 0 deletions doc/components/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,58 @@ Validate the structure of a MOD changelog.txt file.

**Use case**: CI validation of changelog structure before release

### RCon::Exec

Execute a Factorio console command via RCon.

**Requirements**: Factorio server must be running with RCon enabled.

**Arguments**:
- `command` (required) - Console command to execute (e.g. `/server-save`)

**Options**:
- `--host` - RCon host (default: from config, fallback `localhost`)
- `--port` - RCon port (default: from config, fallback `27015`)
- `--password` - RCon password (default: from config)

**Configuration**: Connection settings can be set in the config file:
```ruby
config.rcon.host = "192.168.1.10"
config.rcon.port = 25575
config.rcon.password = "secret"
```

**Output**: Server response, if any. Empty responses are suppressed.

**Errors**:
- `RConConnectionError` - server unreachable
- `RConAuthenticationError` - wrong password

**Examples**:
```bash
factorix rcon exec /server-save
factorix rcon exec --host 192.168.1.10 --port 25575 --password secret /server-save
```

### RCon::Eval

Evaluate a Lua script in a running Factorio server via RCon. Wraps the script in `/c <script>` before sending.

**Requirements**: Factorio server must be running with RCon enabled.

**Arguments**:
- `script` (optional) - Lua script to evaluate; reads from stdin if omitted

**Options**: Same as `rcon exec` (`--host`, `--port`, `--password`)

**Output**: Server response from `rcon.print(...)` calls, if any.

**Examples**:
```bash
factorix rcon eval "rcon.print(game.tick)"
echo "rcon.print(game.tick)" | factorix rcon eval
```

### MOD::Settings::Restore

Restore MOD settings from JSON format.
Expand Down
43 changes: 43 additions & 0 deletions doc/factorix.1
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,49 @@ Remove expired entries only.
.TP
.BR \-\-older\-than =\fIAGE\fR
Remove entries older than AGE (e.g., 30s, 5m, 2h, 7d).
.SH RCON COMMANDS
These commands connect to a running Factorio server via the Source RCON protocol.
The server must be started with RCON enabled (e.g.
.BR \-\-rcon\-port " and " \-\-rcon\-password
flags).
Connection settings can be configured in the Factorix config file under
.BR config.rcon .
.SS factorix rcon exec COMMAND
Execute a Factorio console command via RCon.
.TP
.BR \-\-host =\fIVALUE\fR
RCon host (default: config value or localhost).
.TP
.BR \-\-port =\fIVALUE\fR
RCon port (default: config value or 27015).
.TP
.BR \-\-password =\fIVALUE\fR
RCon password (default: config value).
.PP
.RS
factorix rcon exec /server-save
.RE
.SS factorix rcon eval [SCRIPT]
Evaluate a Lua script in a running Factorio server via RCon.
The script is wrapped in
.B /c
before sending.
If SCRIPT is omitted, reads from stdin.
.TP
.BR \-\-host =\fIVALUE\fR
RCon host (default: config value or localhost).
.TP
.BR \-\-port =\fIVALUE\fR
RCon port (default: config value or 27015).
.TP
.BR \-\-password =\fIVALUE\fR
RCon password (default: config value).
.PP
.RS
factorix rcon eval "rcon.print(game.tick)"
.br
echo "rcon.print(game.tick)" | factorix rcon eval
.RE
.SH MOD COMMANDS
.SS factorix mod list
List installed MOD(s).
Expand Down
1 change: 1 addition & 0 deletions factorix.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "dry-inflector", "~> 1.0"
spec.add_dependency "dry-logger", "~> 1.2"
spec.add_dependency "parslet", "~> 2.0"
spec.add_dependency "rcon-client", "~> 0.5"
spec.add_dependency "retriable", "~> 3.1"
spec.add_dependency "rubyzip", "~> 3.2"
spec.add_dependency "tint_me", "~> 1.1"
Expand Down
8 changes: 8 additions & 0 deletions lib/factorix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ module Factorix
setting :data_dir, constructor: ->(v) { v ? Pathname(v) : nil }
end

# RCON connection settings
setting :rcon do
setting :host, default: "localhost"
setting :port, default: 27015
setting :password, default: nil
end

# HTTP timeout settings
setting :http do
setting :connect_timeout, default: 5
Expand Down Expand Up @@ -129,6 +136,7 @@ def self.load_config(path=nil)
"installed_mod" => "InstalledMOD",
"mac_os" => "MacOS",
"mod" => "MOD",
"rcon" => "RCon",
"game_download_api" => "GameDownloadAPI",
"mod_download_api" => "MODDownloadAPI",
"mod_info" => "MODInfo",
Expand Down
2 changes: 2 additions & 0 deletions lib/factorix/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ class CLI
register "mod settings restore", Commands::MOD::Settings::Restore
register "cache stat", Commands::Cache::Stat
register "cache evict", Commands::Cache::Evict
register "rcon exec", Commands::RCon::Exec
register "rcon eval", Commands::RCon::Eval
end
end
Loading