| Crates.io | arc-automation |
| lib.rs | arc-automation |
| version | 0.9.0 |
| created_at | 2025-10-29 06:54:41.59471+00 |
| updated_at | 2026-01-25 16:57:21.700123+00 |
| description | A scriptable automation tool. |
| homepage | |
| repository | https://github.com/myFavShrimp/arc |
| max_upload_size | |
| id | 1906149 |
| size | 232,714 |
arc (Automatic Remote Controller) is an infrastructure automation tool that uses Lua for scripting. It executes tasks on remote systems via SSH with a flexible API for managing configurations, files, and commands across multiple servers.
Install Rust
Install arc using Cargo:
cargo install arc-automation
This will compile and install the arc binary to the Cargo bin directory (usually ~/.cargo/bin/).
Please make sure you have the required dependencies installed:
Fedora
sudo dnf group install development-tools
sudo dnf install openssl-devel
Ubuntu / Debian
sudo apt install build-essential libssl-dev
Initialize a new arc project with type definitions for LSP support:
arc init /path/to/project
This command creates the project structure, type definitions for code completion and type checking, and a basic arc.lua file with example tasks.
-- Define a target system
targets.systems["web-server"] = {
address = "192.168.1.100",
user = "root",
}
-- Define a simple task
tasks["hello"] = {
handler = function(system)
local result = system:run_command("echo 'Hello from ' $(hostname)")
print(result.stdout)
end
}
Run the task:
arc run -s web-server -t hello
See the examples directory for more complete usage examples.
Targets define the systems where tasks will be executed. There are two types: individual systems and groups.
Systems can be either remote (accessed via SSH) or local (running on the arc host machine).
Remote systems represent individual servers with SSH connection details.
targets.systems["frontend-server"] = {
address = "192.168.1.100",
user = "root",
port = 22, -- optional, defaults to 22
}
targets.systems["api-server"] = {
address = "192.168.1.101",
user = "deploy",
port = 2222,
}
Local systems execute tasks on the machine where arc is running.
targets.systems["localhost"] = {
type = "local"
}
Local systems use the same API as remote systems (same methods for run_command(), file(), directory(), etc.), but operations execute locally instead of over SSH. The address, port, and user properties return nil for local systems.
Groups organize multiple systems.
targets.groups["web-servers"] = {
members = {"frontend-server", "api-server"}
}
targets.groups["prod"] = {
members = {"prod-web-1", "prod-db-1"}
}
Tasks define operations to execute on target systems. Tasks execute in definition order on each system.
tasks["install_nginx"] = {
handler = function(system)
local result = system:run_command("apt install nginx -y")
if result.exit_code ~= 0 then
error("Failed to install nginx: " .. result.stderr)
end
end,
tags = {"nginx", "setup"},
}
tasks["configure_nginx"] = {
requires = {"install_nginx"},
handler = function(system)
local config = system:file("/etc/nginx/nginx.conf")
config.content = "..."
end,
tags = {"nginx"},
}
See Tasks API for all available fields.
arc initInitialize a new arc project with type definitions for LSP support, code completion and type checking.
arc init /path/to/project
arc runExecutes tasks defined in the arc.lua file.
Usage: arc run [OPTIONS] <--tag <TAG>|--all-tags> <--group <GROUP>|--system <SYSTEM>|--all-systems>
Options:
-t, --tag <TAG> Select tasks by tag
-g, --group <GROUP> Run tasks only on specific groups
-s, --system <SYSTEM> Run tasks only on specific systems
-d, --dry-run Print tasks that would be executed without running them
--no-reqs Skip resolution of requires and only run explicitly selected tasks
--all-tags Run all tasks
--all-systems Run on all systems
-h, --help Print help
arc uses a restricted LuaJIT environment. The following standard library modules are available:
require)string.format, string.match, string.gsub, etc.)table.insert, table.remove, table.sort, etc.)math.floor, math.random, etc.)Not available: io, os, debug, coroutine. Use the provided arc APIs (system:run_command(), system:file(), env.get(), etc.) instead.
The global print() function is an alias for log.info().
Tasks are defined by assigning to the global tasks table. Tasks execute in definition order on each system.
handler: Function that implements the task logic
system - The system object to operate ontasks["name"].resulttags (optional): Array of tags for filtering tasks. Tasks are automatically tagged with their name and source file path components (e.g., modules/web/nginx.lua adds tags: modules, web, nginx).
targets (optional): Array of group or system names where this task should run. If omitted, runs on all systems.
requires (optional): Array of tags this task requires. Tasks with matching tags are included when this task is selected. Resolved transitively.
when (optional): Guard predicate that determines if the task should run
boolean - If false, task is skippedon_fail (optional): Behavior when this task fails
"continue" (default): Proceed to next task"skip_system": Skip remaining tasks for this system"abort": Halt execution entirelyimportant (optional): If true, always runs regardless of tag filters, --no-reqs, and skip_system
result: Return value from handler (nil if failed/skipped)state: "success", "failed", or "skipped"error: Error message if failed (nil otherwise)Example:
tasks["check_nginx"] = {
handler = function(system)
return system:run_command("which nginx").exit_code == 0
end
}
tasks["install_nginx"] = {
requires = {"check_nginx"},
when = function()
return tasks["check_nginx"].result == false
end,
handler = function(system)
local result = system:run_command("apt install nginx -y")
if result.exit_code ~= 0 then
error("Failed: " .. result.stderr)
end
end,
on_fail = "skip_system",
}
Requires affect which tasks run, not when. Tasks always execute in definition order. If a task requires something defined later, the required task runs after the requiring task.
The system object represents a connection to a target system (remote or local) and is passed to task handlers.
name: The name of the system as defined in targets.systemstype: The type of system - "remote" or "local"address: The IP address of the system (nil for local systems)port: The SSH port of the system (nil for local systems)user: The SSH user used to connect to the system (nil for local systems)run_command(cmd): Execute a command on the system
cmd (string) - The command to executestdout, stderr, and exit_codefile(path): Get a File object representing a file on the system
path (string) - Path to the filedirectory(path): Get a Directory object representing a directory on the system
path (string) - Path to the directoryExample:
tasks["check_service"] = {
handler = function(system)
log.info("Checking service on " .. system.name .. " at " .. system.address)
local result = system:run_command("systemctl status nginx")
return result.exit_code == 0
end
}
The File object represents a file on a target system and provides access to file content, metadata, and operations.
path: Path to the file (can be read and set; setting the path moves the file)file_name: The name of the file without the directory path (can be read and set)content: Content of the file as binary string (can be read and set)permissions: File permissions (can be read and set as numeric mode; returns nil if file doesn't exist)exists(): Check if file exists
boolean - true if file exists, false otherwisemetadata(): Get file metadata
nil if file doesn't existremove(): Remove the file
directory(): Get the directory containing this file
nil if at root pathExample:
tasks["configure_nginx"] = {
handler = function(system)
local config_file = system:file("/etc/nginx/sites-available/default")
config_file.content = "server {\n listen 80 default_server;\n root /var/www/html;\n}"
config_file.permissions = tonumber("644", 8)
local metadata = config_file:metadata()
if metadata and metadata.size then
log.info("File size: " .. metadata.size .. " bytes")
end
end
}
tasks["manage_file"] = {
handler = function(system)
-- Create, move and then delete a file
local file = system:file("/path/to/file.txt")
file.content = "New content" -- Write to file
file.permissions = tonumber("755", 8) -- Set permissions
file.path = "/new-path/to/renamed-file.txt" -- Rename file
file:remove() -- Delete file
end
}
The Directory object represents a directory on a target system and provides access to directory operations and contents.
path: Path to the directory (can be read and set; setting the path renames the directory)file_name: The name of the directory without the parent path (can be read and set)permissions: Directory permissions (can be read and set as numeric mode; returns nil if directory doesn't exist)create(): Create the directory (including any missing ancestor directories)remove(): Remove the directoryexists(): Check if directory exists
boolean - true if directory exists, false otherwisemetadata(): Get directory metadata
nil if directory doesn't existparent(): Get the parent directory
nil if at root pathentries(): Get directory entries
Example:
tasks["setup_directory"] = {
handler = function(system)
-- Create directory structure
local app_dir = system:directory("/var/www/myapp")
app_dir:create()
-- Set permissions
app_dir.permissions = tonumber("755", 8)
end
}
tasks["list_configs"] = {
handler = function(system)
-- Iterate through directory contents
local dir = system:directory("/etc/nginx/sites-available")
for _, entry in ipairs(dir:entries()) do
-- Each entry is either a File or Directory object
print(entry.path)
print("Permissions: " .. entry.permissions)
local metadata = entry:metadata()
if metadata and metadata.type == "file" and metadata.size then
print("File size: " .. metadata.size .. " bytes")
elseif metadata.type == "directory" then
print("Directory")
end
end
end
}
tasks["manage_directory"] = {
handler = function(system)
-- Create, move and then delete a directory
local dir = system:directory("/path/to/dir")
dir:create() -- Create directory
dir.path = "/path/to/renamed-dir" -- Rename directory
dir:remove() -- Delete directory
end
}
The metadata() method on File or Directory objects returns a table with the following fields, or nil if the file/directory doesn't exist:
path: Path to the file or directorysize: Size in bytes (number, or nil if unavailable)permissions: Permission mode (number, or nil if unavailable)type: Type of the item ("file", "directory", or "unknown")uid: User ID of the owner (number, or nil; always nil on local systems)gid: Group ID of the owner (number, or nil; always nil on local systems)accessed: Last access time as a Unix timestamp (number, or nil if unavailable)modified: Last modification time as a Unix timestamp (number, or nil if unavailable)Example:
tasks["check_metadata"] = {
handler = function(system)
local file = system:file("/etc/hostname")
local metadata = file:metadata()
if metadata then
log.info("File size: " .. (metadata.size or "unknown"))
log.info("File type: " .. metadata.type)
log.info("File permissions: " .. (metadata.permissions or "unknown"))
log.info("Last modified: " .. (metadata.modified or "unknown"))
else
log.info("File does not exist")
end
end
}
The env module provides access to environment variables. arc automatically loads variables from .env files in the project directory. Variables defined in the .env file take precedence over already defined ones.
get(var_name): Get the value of an environment variable
var_name (string) - Name of the environment variableExample:
tasks["deploy_app"] = {
handler = function(system)
local app_version = env.get("APP_VERSION") or "latest"
local deploy_path = env.get("DEPLOY_PATH") or "/var/www"
system:run_command("docker pull myapp:" .. app_version)
system:run_command("docker run -d -v " .. deploy_path .. ":/app myapp:" .. app_version)
end
}
The host module provides functions for interacting with the local system where arc is running. It has the same interface as the system object but operates on the local machine and it's working directory is the one arc was executed in.
run_command(cmd): Execute a command on the local system
cmd (string) - The command to executestdout, stderr, and exit_codefile(path): Get a File object representing a file on the local system
path (string) - Path to the filedirectory(path): Get a Directory object representing a directory on the local system
path (string) - Path to the directoryExample:
tasks["deploy_from_local"] = {
handler = function(system)
-- Read a local template
local config_template = host:file("templates/nginx.conf").content
-- Write to remote system
local remote_config = system:file("/etc/nginx/nginx.conf")
remote_config.content = config_template
-- Restart service
system:run_command("systemctl restart nginx")
end
}
tasks["backup_to_local"] = {
handler = function(system)
-- Read from remote
local remote_config = system:file("/etc/app/config.json").content
-- Save locally
local backup_dir = host:directory("backups/" .. system.name)
backup_dir:create()
local backup_file = host:file("backups/" .. system.name .. "/config.json")
backup_file.content = remote_config
end
}
The format module provides utilities for working with JSON data.
to_json(value): Convert a Lua value to JSON
value (any) - Value to convertto_json_pretty(value): Convert a Lua value to pretty-printed JSON
value (any) - Value to convertfrom_json(json_string): Parse a JSON string to a Lua value
json_string (string) - JSON string to parseExample:
tasks["manage_json_config"] = {
handler = function(system)
-- Read a JSON configuration file
local config_file = system:file("/etc/myapp/config.json")
local config = format.from_json(config_file.content)
-- Modify configuration
config.debug = true
config.log_level = "info"
-- Write back to the file
config_file.content = format.to_json_pretty(config)
end
}
tasks["update_api_config"] = {
handler = function(system)
-- Get current config from an API
local result = system:run_command("curl -s http://localhost:8080/api/config")
local api_config = format.from_json(result.stdout)
-- Update configuration
api_config.settings.cache_ttl = 3600
-- Send updated config back to API
local json_config = format.to_json(api_config)
system:run_command('curl -X POST -H "Content-Type: application/json" -d \'' .. json_config .. '\' http://localhost:8080/api/config')
end
}
The template module provides template rendering capabilities using the Tera template engine.
render(template_content, context): Render a template with given context
template_content (string) - Template contentcontext (table) - Variables to use for template renderingExample:
tasks["configure_web_server"] = {
handler = function(system)
-- Load a template from a local file
local template_content = host:file("templates/nginx.conf.template").content
-- Define context variables
local context = {
worker_processes = 4,
worker_connections = 1024,
server_name = system.name .. ".example.com",
document_root = "/var/www/" .. system.name,
environment = env.get("ENVIRONMENT") or "production"
}
-- Render and deploy configuration
local config = template:render(template_content, context)
system:file("/etc/nginx/nginx.conf").content = config
-- Validate and reload
local validation = system:run_command("nginx -t")
if validation.exit_code == 0 then
system:run_command("systemctl reload nginx")
else
error("Nginx configuration is invalid: " .. validation.stderr)
end
end
}
The log module provides logging functions at various severity levels.
debug(value): Log a debug message
value (any) - Value to loginfo(value): Log an info message
value (any) - Value to logwarn(value): Log a warning message
value (any) - Value to logerror(value): Log an error message
value (any) - Value to logExample:
tasks["provision_database"] = {
handler = function(system)
log.info("Provisioning database on " .. system.name)
local result = system:run_command("systemctl status postgresql")
log.debug("systemctl exit code: " .. result.exit_code)
if result.exit_code ~= 0 then
log.warn("PostgreSQL not running, attempting to install")
local install_result = system:run_command("apt-get install -y postgresql")
if install_result.exit_code ~= 0 then
log.error("Failed to install PostgreSQL: " .. install_result.stderr)
error(install_result.stderr)
end
end
log.debug("PostgreSQL installed and running")
end
}
arc provides Language Server Protocol (LSP) support for Lua code editing with autocomplete, type checking, and inline documentation.
Install the Lua Language Server for the editor being used.
Initialize an arc project to generate type definitions:
arc init /path/to/project
The init command creates .luarc.json and type definition files that enable the Lua Language Server to recognize arc's API types.
Contributions are welcome! Please feel free to submit any issue or pull request.
This project is licensed under the MIT License - see the LICENSE file for details.