ork

package module
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

README

Ork

Ork is a Go package for SSH-based server automation. Think of it like Ansible, but in Go - you define Nodes (remote servers), organize them into Groups, and run commands or skills against them individually or at scale via Inventory.

Installation

go get github.com/dracory/ork

Quick Start

The core concept is the Node - a representation of a remote server:

package main

import (
    "log"
    "github.com/dracory/ork"
    "github.com/dracory/ork/config"
)

func main() {
    // Create a node (remote server) - multiple ways:
    
    // Option 1: From host (most common)
    node := ork.NewNodeForHost("server.example.com")
    
    // Option 2: From config (useful for complex setups)
    cfg := config.NodeConfig{
        SSHHost: "server.example.com",
        SSHPort: "22",
    }
    node := ork.NewNodeFromConfig(cfg)
    
    // Run a command
    results := node.RunCommand("uptime")
    result := results.Results["server.example.com"]
    if result.Error != nil {
        log.Fatal(result.Error)
    }
    log.Println(result.Message)
}

Vault (Secure Secrets Management)

Ork provides secure vault support for managing sensitive data like passwords and API keys. The vault uses encrypted storage with two loading strategies:

Secrets are loaded into a memory map, keeping them isolated from the rest of the process:

// Simple one-liner with interactive password prompt
secrets, err := ork.VaultFileToKeysWithPrompt(".env.vault")
if err != nil {
    log.Fatal(err)
}
dbPassword := secrets["DATABASE_PASSWORD"]

// Or separate prompt and load for custom workflows
password, err := ork.PromptPassword("Vault password: ")
if err != nil {
    log.Fatal(err)
}
secrets, err := ork.VaultFileToKeys(".env.vault", password)
if err != nil {
    log.Fatal(err)
}
Load to Environment (For .env Compatibility)

Secrets are loaded into environment variables for compatibility with tools that expect .env files:

// Simple one-liner with interactive password prompt
if err := ork.VaultFileToEnvWithPrompt(".env.vault"); err != nil {
    log.Fatal(err)
}
// Secrets now available via os.Getenv()

// Or separate prompt and load
password, err := ork.PromptPassword("Vault password: ")
if err != nil {
    log.Fatal(err)
}
if err := ork.VaultFileToEnv(".env.vault", password); err != nil {
    log.Fatal(err)
}
Available Functions

Load to Memory:

  • VaultFileToKeys(filePath, password) - Load from file to map
  • VaultContentToKeys(content, password) - Load from string to map
  • VaultFileToKeysWithPrompt(filePath) - Load from file with interactive prompt
  • VaultContentToKeysWithPrompt(content) - Load from string with interactive prompt

Load to Environment:

  • VaultFileToEnv(filePath, password) - Load from file to environment
  • VaultContentToEnv(content, password) - Load from string to environment
  • VaultFileToEnvWithPrompt(filePath) - Load from file with interactive prompt
  • VaultContentToEnvWithPrompt(content) - Load from string with interactive prompt

Utilities:

  • PromptPassword(prompt) - Securely prompt for password from stdin (no echo)
Creating a Vault File

Use the envenc CLI tool to create encrypted vault files:

# Initialize a new vault
envenc init .env.vault

# Set secrets
envenc set DATABASE_PASSWORD "my-secret-password"
envenc set API_KEY "my-api-key"

# List keys
envenc list .env.vault
Design Trade-offs

ToKeys functions:

  • Secrets stored in memory map
  • No global exposure
  • Recommended for security-sensitive applications
  • Caller manages secret lifecycle

ToEnv functions:

  • Secrets dumped to environment variables
  • Global exposure via os.Getenv
  • For compatibility with .env-based tooling
  • Use when external tools expect environment variables

Configuration

Use the fluent API to configure connection settings:

// From host
node := ork.NewNodeForHost("server.example.com").
    SetPort("2222").
    SetUser("deploy").
    SetKey("production.prv")

results := node.RunCommand("uptime")
result := results.Results["server.example.com"]
if result.Error != nil {
    log.Fatal(result.Error)
}
output := result.Message

Persistent Connections

For multiple operations, establish a persistent connection:

node := ork.NewNodeForHost("server.example.com").
    SetPort("2222").
    SetUser("deploy")

if err := node.Connect(); err != nil {
    log.Fatal(err)
}
defer node.Close()

// These commands reuse the same SSH connection
results1 := node.RunCommand("uptime")
results2 := node.RunCommand("df -h")
output1 := results1.Results["server.example.com"].Message
output2 := results2.Results["server.example.com"].Message

Skills

Run pre-built automation tasks (skills) against a node:

node := ork.NewNodeForHost("server.example.com").
    SetArg("username", "alice").
    SetArg("shell", "/bin/bash")

results := node.RunSkill(skills.NewUserCreate())
result := results.Results["server.example.com"]
if result.Error != nil {
    log.Fatalf("Skill failed: %v", result.Error)
}
if result.Changed {
    log.Printf("User created: %s", result.Message)
} else {
    log.Println("User already exists - no changes made")
}
Available Skills
ork Package skill Package String Args Description
SkillPing IDPing ping - Check SSH connectivity
SkillAptUpdate IDAptUpdate apt-update - Refresh package database
SkillAptUpgrade IDAptUpgrade apt-upgrade - Install available updates
SkillAptStatus IDAptStatus apt-status - Show available updates
SkillReboot IDReboot reboot - Reboot server
SkillSwapCreate IDSwapCreate swap-create size (GB) Create swap file
SkillSwapDelete IDSwapDelete swap-delete - Remove swap file
SkillSwapStatus IDSwapStatus swap-status - Show swap status
SkillUserCreate IDUserCreate user-create username Create user with sudo
SkillUserDelete IDUserDelete user-delete username Delete user
SkillUserStatus IDUserStatus user-status username (opt) Show user info
SkillFail2banInstall IDFail2banInstall fail2ban-install - Install and configure fail2ban
SkillFail2banStatus IDFail2banStatus fail2ban-status - Show fail2ban status
SkillUfwInstall IDUfwInstall ufw-install - Install UFW firewall
SkillUfwStatus IDUfwStatus ufw-status - Show UFW status
SkillUfwAllowMariadb IDUfwAllowMariadb ufw-allow-mariadb - Allow MariaDB through UFW
SkillMariadbInstall IDMariadbInstall mariadb-install - Install MariaDB server
SkillMariadbStatus IDMariadbStatus mariadb-status - Show MariaDB status
SkillMariadbSecure IDMariadbSecure mariadb-secure - Secure MariaDB installation
SkillMariadbBackup IDMariadbBackup mariadb-backup database (opt) Backup database
SkillMariadbBackupEncrypt IDMariadbBackupEncrypt mariadb-backup-encrypt - Encrypted backup
SkillMariadbChangePort IDMariadbChangePort mariadb-change-port port Change MariaDB port
SkillMariadbCreateDB IDMariadbCreateDB mariadb-create-db database Create database
SkillMariadbCreateUser IDMariadbCreateUser mariadb-create-user username, password Create DB user
SkillMariadbEnableEncryption IDMariadbEnableEncryption mariadb-enable-encryption - Enable encryption at rest
SkillMariadbEnableSSL IDMariadbEnableSSL mariadb-enable-ssl - Enable SSL connections
SkillMariadbListDBs IDMariadbListDBs mariadb-list-dbs - List databases
SkillMariadbListUsers IDMariadbListUsers mariadb-list-users - List DB users
SkillMariadbSecurityAudit IDMariadbSecurityAudit mariadb-security-audit - Run security audit
SkillSecurityAideInstall IDSecurityAideInstall security-aide-install - Install AIDE IDS
SkillSecurityAuditdInstall IDSecurityAuditdInstall security-auditd-install - Install audit daemon
SkillSecurityKernelHarden IDSecurityKernelHarden security-kernel-harden - Apply kernel hardening
SkillSecuritySSHChangePort IDSecuritySSHChangePort security-ssh-change-port port Change SSH port
SkillSecuritySSHHarden IDSecuritySSHHarden security-ssh-harden - Harden SSH config

Inventory (Multi-Node Operations)

Manage multiple servers with the same API:

// Create inventory
inv := ork.NewInventory()

// Add nodes to groups
webGroup := ork.NewGroup("webservers")
webGroup.AddNode(ork.NewNodeForHost("web1.example.com"))
webGroup.AddNode(ork.NewNodeForHost("web2.example.com"))
webGroup.SetArg("env", "production")
inv.AddGroup(webGroup)

// Run skill on entire inventory
results := inv.RunSkill(skills.NewPing())

// Check summary
summary := results.Summary()
fmt.Printf("Total: %d, Changed: %d, Failed: %d\n",
    summary.Total, summary.Changed, summary.Failed)

// Check individual results
for host, result := range results.Results {
    if result.Error != nil {
        log.Printf("%s failed: %v", host, result.Error)
    }
}

Idempotency

All skills support idempotent execution. Use CheckSkill() to preview changes:

// Check if changes would be made (dry-run)
results := node.CheckSkill(skills.NewAptUpgrade())
result := results.Results["server.example.com"]

if result.Changed {
    log.Printf("Would upgrade packages: %s", result.Message)
    // Now actually run it
    results = node.RunSkill(skills.NewAptUpgrade())
}
Result Structure

Results are returned as types.Results with per-node access:

type Results struct {
    Results map[string]Result  // Key is node hostname
}

func (r Results) Summary() Summary

type Result struct {
    Changed bool              // Whether changes were made
    Message string            // Human-readable description
    Details map[string]string // Additional information
    Error   error             // Non-nil if execution failed
}

type Summary struct {
    Total     int
    Changed   int
    Unchanged int
    Failed    int
}
Direct Skill Access (Advanced)

For programmatic skill handling, use the skill package directly:

import (
    "github.com/dracory/ork/skill"
    "github.com/dracory/ork/skills"
)

// Execute directly with config
aptUpgrade := skills.NewAptUpgrade()
aptUpgrade.SetConfig(cfg)
result := aptUpgrade.Run()

// Or check before running via CheckSkill
results := node.CheckSkill(skills.NewSwapCreate())
result := results.Results["server.example.com"]
if !result.Changed {
    log.Println("Swap already exists, skipping...")
    return
}

Dry-Run Mode

Preview what changes would be made without actually executing commands on the server. Safety is enforced at the SSH execution layer - no commands execute on the server in dry-run mode.

Enable Dry-Run
// At node level
node := ork.NewNodeForHost("server.example.com").
    SetDryRunMode(true)
results := node.RunSkill(skills.NewAptUpgrade())
// Commands are logged but not executed

// At group level
webGroup := ork.NewGroup("webservers")
webGroup.SetDryRunMode(true)
webGroup.AddNode(node1)
webGroup.AddNode(node2)
// All nodes inherit dry-run mode

// At inventory level
inv := ork.NewInventory()
inv.SetDryRunMode(true)
inv.AddGroup(webGroup)
results := inv.RunCommand("uptime")
// All groups and nodes inherit dry-run mode
How It Works
  1. Safety at execution layer: ssh.Run() checks cfg.IsDryRunMode and returns "[dry-run]" without executing commands
  2. Automatic propagation: Dry-run mode propagates from Inventory → Groups → Nodes at execution time
  3. Thread-safe: Uses mutex protection for concurrent access to dry-run state
Detecting Dry-Run in Skills
func (s *MySkill) Run() skill.Result {
    output, _ := ssh.Run(s.cfg, "apt-get upgrade -y")

    if output == "[dry-run]" {
        return skill.Result{
            Changed: true,
            Message: "Would run: apt-get upgrade -y",
        }
    }
    // Normal execution handling...
}

Note: Even if a skill doesn't check for the [dry-run] marker, safety is guaranteed - no commands execute on the server when dry-run mode is enabled.

Advanced Usage

Inspecting Configuration
node := ork.NewNodeForHost("server.example.com").
    SetPort("2222").
    SetUser("deploy")

fmt.Printf("Host: %s\n", node.GetHost())
fmt.Printf("Port: %s\n", node.GetPort())
fmt.Printf("User: %s\n", node.GetUser())

// Get full config for integration with internal packages
cfg := node.GetNodeConfig()
Custom Skills

Extend Ork with custom automation tasks by implementing the Skill interface:

Registering Custom Skills

Register your custom skills to use them via node.RunSkillByID("custom-id"):

import (
    "github.com/dracory/ork"
    "github.com/dracory/ork/skill"
)

// Create a custom skill
customSkill := skill.NewBaseSkill()
customSkill.SetID("install-docker")
customSkill.SetDescription("Install Docker on the server")

// Register it globally
registry, err := ork.GetGlobalSkillRegistry()
if err != nil {
    log.Fatalf("Failed to get registry: %v", err)
}
if err := registry.SkillRegister(customSkill); err != nil {
    log.Fatalf("Failed to register skill: %v", err)
}

// Now use it like any built-in skill
node := ork.NewNodeForHost("server.example.com")
result := node.RunSkillByID("install-docker")
Custom Skills with Full Idempotency

For full idempotency support, implement all methods:

type MyCustomSkill struct{}

func (s *MyCustomSkill) GetID() string { return "my-task" }
func (s *MyCustomSkill) Description() string { return "Does something" }

// Check() - returns true if changes needed
func (s *MyCustomSkill) Check(cfg config.NodeConfig) (bool, error) {
    // Check if already configured
    // Skills implement Check for use with CheckSkill()
    output, _ := ssh.Run(cfg, types.Command{Command: "cat /etc/my-config"})
    return !strings.Contains(output, "configured"), nil
}

// Run() - execute and return Result
func (s *MyCustomSkill) Run(cfg config.NodeConfig) skill.Result {
    needsChange, _ := s.Check(cfg)
    if !needsChange {
        return skill.Result{
            Changed: false,
            Message: "Already configured",
        }
    }

    // Apply changes...
    _, err := ssh.Run(cfg, types.Command{Command: "setup-command"})
    if err != nil {
        return skill.Result{Changed: false, Error: err}
    }

    return skill.Result{
        Changed: true,
        Message: "Configuration applied",
    }
}

Internal Packages

For advanced use cases or when you need fine-grained control, you can use the internal packages directly:

  • ssh - SSH connection utilities and command execution
  • config - Configuration types for remote operations
  • skill - Base interfaces and registry for organizing skills
  • skills - Reusable skill implementations (ping, apt, reboot, swap, user)

For advanced use cases, use the internal packages directly:

package main

import (
    "log"

    "github.com/dracory/ork/config"
    "github.com/dracory/ork/skills"
)

func main() {
    cfg := config.NodeConfig{
        SSHHost:  "db3.sinevia.com",
        SSHPort:  "40022",
        SSHKey:   "2024_sinevia.prv",
        RootUser: "root",
    }

    // Ping server to check connectivity
    ping := skills.NewPing()
    ping.SetConfig(cfg)
    result := ping.Run()
    if result.Error != nil {
        log.Fatal(result.Error)
    }

    // Update packages
    aptUpdate := skills.NewAptUpdate()
    aptUpdate.SetConfig(cfg)
    result = aptUpdate.Run()
    if result.Error != nil {
        log.Fatal(result.Error)
    }

    // Create a 2GB swap file
    cfg.Args = map[string]string{"size": "2"}
    swapCreate := skills.NewSwapCreate()
    swapCreate.SetConfig(cfg)
    result = swapCreate.Run()
    if result.Error != nil {
        log.Fatal(result.Error)
    }
}
Package Overview
  • ork - Main API: NodeInterface, InventoryInterface, GroupInterface, RunnerInterface
  • types - Shared types: Result, Results, Summary
  • runnable - RunnerInterface for Node, Group, and Inventory
  • config - Configuration types
  • ssh - SSH client with connection management
  • skill - Skill interface and registry
  • skills - Built-in skill implementations

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). You can find a copy of the license at https://www.gnu.org/licenses/agpl-3.0.en.html

For commercial use, please use my contact page to obtain a commercial license.

Documentation

Overview

Package ork provides a framework for remote server automation.

Package ork provides a simple, intuitive API for SSH-based server automation.

The core concept is the Node - a representation of a remote server that you can connect to and run commands or playbooks against.

Basic usage:

node := ork.NewNode("server.example.com")
output, err := node.Run("uptime")

With configuration:

node := ork.NewNode("server.example.com").
    SetPort("2222").
    SetUser("deploy")
output, err := node.Run("uptime")

Persistent connections for multiple operations:

node := ork.NewNode("server.example.com")
if err := node.Connect(); err != nil {
    log.Fatal(err)
}
defer node.Close()

output1, _ := node.Run("uptime")
output2, _ := node.Run("df -h")

Running playbooks:

node := ork.NewNode("server.example.com").
    SetArg("username", "alice")
err := node.Playbook("user-create")

For advanced use cases, the internal packages remain accessible:

  • config - Configuration types
  • ssh - SSH client
  • types - Shared types (PlaybookInterface, Registry, Result, Command)
  • playbook - Playbook interface (re-exports from types for backward compatibility)
  • playbooks - Built-in playbook implementations

Package ork provides a framework for remote server automation.

Index

Constants

View Source
const (
	// SkillPing checks SSH connectivity
	SkillPing = skills.IDPing

	// SkillAptUpdate refreshes the package database
	SkillAptUpdate = skills.IDAptUpdate

	// SkillAptUpgrade installs available updates
	SkillAptUpgrade = skills.IDAptUpgrade

	// SkillAptStatus shows available updates
	SkillAptStatus = skills.IDAptStatus

	// SkillReboot reboots the server
	SkillReboot = skills.IDReboot

	// SkillSwapCreate creates a swap file (requires "size" arg in GB)
	SkillSwapCreate = skills.IDSwapCreate

	// SkillSwapDelete removes the swap file
	SkillSwapDelete = skills.IDSwapDelete

	// SkillSwapStatus shows swap status
	SkillSwapStatus = skills.IDSwapStatus

	// SkillUserCreate creates a user with sudo (requires "username" arg)
	SkillUserCreate = skills.IDUserCreate

	// SkillUserDelete deletes a user (requires "username" arg)
	SkillUserDelete = skills.IDUserDelete

	// SkillUserList lists all non-system users
	SkillUserList = skills.IDUserList

	// SkillUserStatus shows user info (requires "username" arg)
	SkillUserStatus = skills.IDUserStatus
)

Skill ID constants for use with RunByID. These constants provide compile-time safety and IDE autocomplete for skill IDs. They are aliases to the constants in the skills package.

Example:

node := ork.NewNodeForHost("server.example.com")
err := node.Run(ork.SkillPing)

Variables

This section is empty.

Functions

func GetGlobalSkillRegistry added in v0.11.0

func GetGlobalSkillRegistry() (*types.Registry, error)

GetGlobalSkillRegistry returns the global skill registry singleton. This is syntactic sugar for user convenience - it lazily initializes and returns the global registry with all built-in skills pre-registered.

For most use cases, users should call this function. For testing or custom configurations, use NewDefaultRegistry() to create isolated registries.

The registry is lazily initialized on first call using sync.Once to ensure thread-safe singleton behavior. Returns an error if initialization fails.

func NewDefaultRegistry added in v0.10.0

func NewDefaultRegistry() (*types.Registry, error)

NewDefaultRegistry creates a new skill registry with all built-in skills registered. This creates a fresh registry instance (not a singleton), which is useful for: - Testing with isolated registries - Custom configurations without global state - Multiple independent registries in the same application

For most production use cases, use GetGlobalSkillRegistry() instead for convenience. Returns an error if any skill registration fails.

func NewSkillRegistry added in v0.11.0

func NewSkillRegistry() *types.Registry

NewSkillRegistry creates a new empty skill registry. This is a convenience method (sugar) for types.NewRegistry() with a more intuitive name. This creates a fresh empty registry instance, which is useful for: - Testing with isolated registries - Custom configurations with selective skill registration - Multiple independent registries in the same application

Returns an empty registry ready for custom skill registration.

func PromptForBool added in v0.11.0

func PromptForBool(prompt string) (bool, error)

PromptForBool prompts for a boolean value (yes/no).

func PromptForBoolWithDefault added in v0.11.0

func PromptForBoolWithDefault(prompt string, defaultValue bool) (bool, error)

PromptForBoolWithDefault prompts for a boolean value with a default.

func PromptForInt added in v0.11.0

func PromptForInt(prompt string) (int, error)

PromptForInt prompts for an integer value.

func PromptForIntWithDefault added in v0.11.0

func PromptForIntWithDefault(prompt string, defaultValue int) (int, error)

PromptForIntWithDefault prompts for an integer value with a default.

func PromptForPassword added in v0.11.0

func PromptForPassword(prompt string) (string, error)

PromptForPassword prompts for a password (hidden input).

func PromptForPasswordWithConfirmation added in v0.11.0

func PromptForPasswordWithConfirmation(prompt string) (string, error)

PromptForPasswordWithConfirmation prompts for a password with confirmation.

func PromptForString added in v0.11.0

func PromptForString(prompt string) (string, error)

PromptForString prompts for a string value.

func PromptForStringWithDefault added in v0.11.0

func PromptForStringWithDefault(prompt, defaultValue string) (string, error)

PromptForStringWithDefault prompts for a string value with a default.

func PromptMultiple added in v0.11.0

func PromptMultiple(configs []types.PromptConfig, existingValues ...map[string]string) (types.PromptResult, error)

PromptMultiple prompts for multiple variables using a configuration. Returns a map of variable names to user-provided values. Skips prompts for variables that already exist in the provided values map. Optionally pass existing values as a second argument.

Example:

prompts := []types.PromptConfig{
    {Name: "username", Prompt: "Username", Default: "admin", Required: true},
    {Name: "password", Prompt: "Password", Private: true, Confirm: true, Required: true},
}
results, err := ork.PromptMultiple(prompts)
if err != nil {
    log.Fatal(err)
}
username := results["username"]
password := results["password"]

func PromptPassword added in v0.10.0

func PromptPassword(prompt string) (string, error)

PromptPassword securely prompts for a password from stdin. The password is not echoed to the terminal.

func PromptWithOptions added in v0.11.0

func PromptWithOptions(prompt string, options []string) (int, error)

PromptWithOptions prompts the user to select from a list of options. Returns the index of the selected option (0-based).

func VaultContentToEnv added in v0.10.0

func VaultContentToEnv(vaultContent, vaultPassword string) (err error)

VaultContentToEnv hydrates environment variables from a vault content string.

func VaultContentToEnvWithPrompt added in v0.10.0

func VaultContentToEnvWithPrompt(vaultContent string) (err error)

VaultContentToEnvWithPrompt prompts for password and hydrates environment variables from a vault content string.

func VaultContentToKeys added in v0.10.0

func VaultContentToKeys(vaultContent, vaultPassword string) (keys map[string]string, err error)

VaultContentToKeys loads keys from a vault content string.

func VaultContentToKeysWithPrompt added in v0.10.0

func VaultContentToKeysWithPrompt(vaultContent string) (keys map[string]string, err error)

VaultContentToKeysWithPrompt prompts for password and loads keys from a vault content string.

func VaultFileToEnv added in v0.10.0

func VaultFileToEnv(vaultFilePath, vaultPassword string) (err error)

VaultFileToEnv decrypts vault and hydrates environment variables.

func VaultFileToEnvWithPrompt added in v0.10.0

func VaultFileToEnvWithPrompt(vaultFilePath string) (err error)

VaultFileToEnvWithPrompt prompts for password and hydrates environment variables from a vault file.

func VaultFileToKeys added in v0.10.0

func VaultFileToKeys(vaultFilePath string, vaultPassword string) (keys map[string]string, err error)

VaultFileToKeys loads keys from a vault file.

func VaultFileToKeysWithPrompt added in v0.10.0

func VaultFileToKeysWithPrompt(vaultFilePath string) (keys map[string]string, err error)

VaultFileToKeysWithPrompt prompts for password and loads keys from a vault file.

Types

type GroupInterface added in v0.9.0

type GroupInterface interface {
	// RunnerInterface is embedded for command and playbook execution.
	// Operations run on all nodes in this group only.
	RunnerInterface

	// GetName returns the group's name.
	GetName() string

	// AddNode adds a node to this group.
	// The node can be configured using the returned NodeInterface.
	AddNode(node NodeInterface) GroupInterface

	// GetNodes returns all nodes in this group.
	GetNodes() []NodeInterface

	// SetArg sets an argument for this group.
	// Group arguments are inherited by all nodes in the group.
	SetArg(key, value string) GroupInterface

	// GetArg retrieves an argument value by key.
	// Returns empty string if not set.
	GetArg(key string) string

	// GetArgs returns a copy of all arguments defined for this group.
	GetArgs() map[string]string
}

GroupInterface defines operations for managing a group of nodes. It embeds RunnerInterface for executing operations on the group's nodes.

func NewGroup added in v0.9.0

func NewGroup(name string) GroupInterface

NewGroup creates a new group with the given name.

type InventoryInterface added in v0.9.0

type InventoryInterface interface {
	// RunnerInterface is embedded for command and playbook execution.
	// Operations run concurrently across all nodes in the inventory.
	RunnerInterface

	// AddGroup adds a group to the inventory.
	AddGroup(group GroupInterface) InventoryInterface

	// GetGroup retrieves a group by name.
	// Returns nil if the group does not exist.
	GetGroupByName(name string) GroupInterface

	// AddNode adds a node directly to the inventory (not in any specific group).
	// The node can be configured using the returned NodeInterface.
	AddNode(node NodeInterface) InventoryInterface

	// GetNodes returns all nodes in the inventory across all groups.
	GetNodes() []NodeInterface

	// SetMaxConcurrency sets the maximum number of concurrent operations.
	// Default is 1 (sequential execution). Set to 0 for unlimited.
	SetMaxConcurrency(max int) InventoryInterface
}

InventoryInterface defines operations for managing a collection of nodes organized into groups. It embeds RunnerInterface for executing operations across all nodes in the inventory.

func NewInventory added in v0.9.0

func NewInventory() InventoryInterface

NewInventory creates a new empty inventory.

type NodeInterface

type NodeInterface interface {
	// RunnerInterface defines operations that can be performed on the node.
	// NodeInterface embeds RunnerInterface for unified API with Group and Inventory.
	RunnerInterface

	// GetArg retrieves a single argument value by key.
	// Returns empty string if the argument is not set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetArg("username", "alice")
	//	fmt.Println(node.GetArg("username"))  // Output: alice
	GetArg(key string) string

	// GetArgs returns a copy of the entire arguments map.
	// Modifying the returned map will not affect the node's internal state.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetArg("username", "alice")
	//	args := node.GetArgs()
	//	fmt.Println(args["username"])  // Output: alice
	GetArgs() map[string]string

	// GetNodeConfig returns a copy of the underlying types.NodeConfig.
	// This allows integration with code that uses the config package directly.
	// The returned configuration includes all accumulated settings (host, port, user, key, args).
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").
	//	    SetPort("2222").
	//	    SetUser("deploy")
	//	cfg := node.GetNodeConfig()
	//	fmt.Printf("Connecting to %s\n", cfg.SSHAddr())
	GetNodeConfig() types.NodeConfig

	// GetHost returns the configured SSH host (hostname or IP address).
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com")
	//	fmt.Println(node.GetHost())  // Output: server.example.com
	GetHost() string

	// GetUser returns the configured SSH user.
	// Returns "root" if not explicitly set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetUser("deploy")
	//	fmt.Println(node.GetUser())  // Output: deploy
	GetUser() string

	// GetKey returns the configured SSH private key filename.
	// Returns "id_rsa" if not explicitly set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetKey("production.prv")
	//	fmt.Println(node.GetKey())  // Output: production.prv
	GetKey() string

	// GetPort returns the configured SSH port.
	// Returns "22" if not explicitly set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetPort("2222")
	//	fmt.Println(node.GetPort())  // Output: 2222
	GetPort() string

	// SetPort sets the SSH port for the connection.
	// Returns the NodeInterface to enable method chaining.
	// Default is "22" if not set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetPort("2222")
	SetPort(port string) NodeInterface

	// SetUser sets the SSH user for the connection.
	// Returns the NodeInterface to enable method chaining.
	// Default is "root" if not set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetUser("deploy")
	SetUser(user string) NodeInterface

	// SetKey sets the SSH private key filename for authentication.
	// The key is resolved to ~/.ssh/<keyname>.
	// Returns the NodeInterface to enable method chaining.
	// Default is "id_rsa" if not set.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").SetKey("production.prv")
	SetKey(key string) NodeInterface

	// SetArg adds a single argument to the arguments map.
	// This adds to existing arguments without replacing them.
	// Arguments are passed to skills for configuration.
	// Returns the NodeInterface to enable method chaining.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com").
	//	    SetArg("username", "alice").
	//	    SetArg("shell", "/bin/bash")
	SetArg(key, value string) NodeInterface

	// SetArgs replaces the entire arguments map with the provided map.
	// Any existing arguments are discarded.
	// Arguments are passed to skills for configuration.
	// Returns the NodeInterface to enable method chaining.
	//
	// Example:
	//
	//	args := map[string]string{
	//	    "username": "alice",
	//	    "shell": "/bin/bash",
	//	}
	//	node := ork.NewNode("server.example.com").SetArgs(args)
	SetArgs(args map[string]string) NodeInterface

	// Connect establishes a persistent SSH connection to the remote server.
	// The connection is maintained until Close() is called.
	// Subsequent RunCommand() and RunSkill() calls will reuse this connection.
	//
	// Returns an error if the connection fails, with a descriptive message
	// including the host and port.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com")
	//	if err := node.Connect(); err != nil {
	//	    log.Fatalf("Failed to connect: %v", err)
	//	}
	//	defer node.Close()
	Connect() error

	// Close terminates the persistent SSH connection and releases resources.
	// After calling Close(), IsConnected() will return false.
	// It is safe to call Close() multiple times or on a non-connected node.
	//
	// Returns an error if closing the connection fails.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com")
	//	node.Connect()
	//	defer node.Close()
	Close() error

	// IsConnected returns true if a persistent SSH connection is currently active.
	// Returns false if Connect() has not been called or if Close() was called.
	//
	// Example:
	//
	//	node := ork.NewNode("server.example.com")
	//	fmt.Println(node.IsConnected())  // Output: false
	//	node.Connect()
	//	fmt.Println(node.IsConnected())  // Output: true
	//	node.Close()
	//	fmt.Println(node.IsConnected())  // Output: false
	IsConnected() bool
}

NodeInterface defines the contract for managing a remote server via SSH. Implementations must support configuration via setter methods, connection management, command execution, and playbook execution.

The interface provides two patterns for configuration:

  • Fluent builder pattern: Chain setter methods for readable configuration
  • Getter methods: Inspect current configuration state

Connection management is explicit, allowing users to control the SSH connection lifecycle. Operations (RunCommand, RunPlaybook) can work with or without a persistent connection.

Example usage with fluent builder pattern:

node := ork.NewNode("server.example.com").
    SetPort("2222").
    SetUser("deploy").
    SetKey("production.prv")

if err := node.Connect(); err != nil {
    log.Fatal(err)
}
defer node.Close()

output, err := node.RunCommand("uptime")
if err != nil {
    log.Fatal(err)
}
fmt.Println(output)

Example usage without persistent connection:

node := ork.NewNode("server.example.com")
output, err := node.RunCommand("uptime")  // Creates one-time connection

Example usage with skills:

node := ork.NewNode("server.example.com").
    SetArg("username", "alice").
    SetArg("shell", "/bin/bash")

if err := node.Run("user-create"); err != nil {
    log.Fatal(err)
}

func NewNode

func NewNode() NodeInterface

NewNode creates a new Node with default configuration values. Unlike NewNodeForHost, this function takes no arguments and creates a node with an empty host. Use SetArg or SetArgs to configure the node.

Default values:

  • Host: "" (empty - must be set before connecting)
  • Port: "22"
  • User: "root"
  • Key: "id_rsa"
  • Args: empty map

Example:

node := ork.NewNode().
    SetHost("server.example.com").
    SetPort("2222").
    SetUser("deploy")

if err := node.Connect(); err != nil {
    log.Fatal(err)
}

func NewNodeForHost added in v0.3.0

func NewNodeForHost(host string) NodeInterface

NewNode creates a new Node with default configuration values. The host parameter specifies the remote server (hostname or IP address).

Default values:

  • Port: "22"
  • User: "root"
  • Key: "id_rsa"
  • Args: empty map

The returned NodeInterface can be configured using setter methods (SetPort, SetUser, SetKey, SetArg, SetArgs) before connecting.

Example:

node := ork.NewNode("server.example.com")
// Equivalent to:
// Node{
//     cfg: types.NodeConfig{
//         SSHHost: "server.example.com",
//         SSHPort: "22",
//         RootUser: "root",
//         SSHKey: "id_rsa",
//         Args: map[string]string{},
//     },
//     connected: false,
// }

Example with configuration:

node := ork.NewNode("server.example.com").
    SetPort("2222").
    SetUser("deploy").
    SetKey("production.prv")

func NewNodeFromConfig added in v0.3.0

func NewNodeFromConfig(cfg types.NodeConfig) NodeInterface

NewNodeFromConfig creates a new Node from an existing types.NodeConfig. This is useful when you have a pre-built configuration and want to create a Node from it directly.

The config is copied internally, so modifications to the original config after calling this function will not affect the Node.

Example:

cfg := types.NodeConfig{
    SSHHost:  "server.example.com",
    SSHPort:  "2222",
    RootUser: "deploy",
    SSHKey:   "production.prv",
    Args: map[string]string{"env": "production"},
}
node := ork.NewNodeFromConfig(cfg)

if err := node.Connect(); err != nil {
    log.Fatal(err)
}

type RunnerInterface added in v0.11.0

type RunnerInterface interface {
	// RunCommand executes a shell command and returns the output.
	// For Inventory, runs concurrently across all nodes.
	RunCommand(cmd string) types.Results

	// Run executes any runnable (command or skill).
	// For Inventory, runs concurrently across all nodes.
	Run(runnable types.RunnableInterface) types.Results

	// RunByID executes a skill by ID from the registry.
	// Deprecated: Use Run() instead.
	RunByID(id string, opts ...types.RunnableOptions) types.Results

	// Check runs the runnable's check mode to determine if changes would be made.
	// Sets Changed=true on result if changes are needed.
	Check(runnable types.RunnableInterface) types.Results

	// GetLogger returns the logger. Returns slog.Default() if not set.
	GetLogger() *slog.Logger

	// SetLogger sets a custom logger. Returns self for chaining.
	SetLogger(logger *slog.Logger) RunnerInterface

	// SetDryRunMode sets whether to simulate execution without making changes.
	// When true, ssh.Run() will log commands and return "[dry-run]" marker instead of executing.
	// Returns self for chaining.
	SetDryRunMode(dryRun bool) RunnerInterface

	// GetDryRunMode returns true if dry-run mode is enabled.
	// When true, commands are logged but not executed on the server.
	GetDryRunMode() bool
}

RunnerInterface defines operations that can be performed on either a single Node or an Inventory of nodes.

Directories

Path Synopsis
internal
Package skills provides built-in reusable skill implementations for common server automation tasks.
Package skills provides built-in reusable skill implementations for common server automation tasks.
apt
Package apt provides playbooks for managing Debian/Ubuntu packages via apt.
Package apt provides playbooks for managing Debian/Ubuntu packages via apt.
fail2ban
Package fail2ban provides playbooks for managing the fail2ban intrusion prevention system.
Package fail2ban provides playbooks for managing the fail2ban intrusion prevention system.
mariadb
Package mariadb provides playbooks for managing MariaDB database servers.
Package mariadb provides playbooks for managing MariaDB database servers.
ping
Package ping provides a skill for testing SSH connectivity to remote servers.
Package ping provides a skill for testing SSH connectivity to remote servers.
reboot
Package reboot provides a skill for rebooting remote servers.
Package reboot provides a skill for rebooting remote servers.
security
Package security provides playbooks for system security hardening and configuration.
Package security provides playbooks for system security hardening and configuration.
swap
Package swap provides playbooks for managing swap files on Linux systems.
Package swap provides playbooks for managing swap files on Linux systems.
ufw
Package ufw provides playbooks for managing the Uncomplicated Firewall (UFW).
Package ufw provides playbooks for managing the Uncomplicated Firewall (UFW).
user
Package user provides playbooks for managing Linux user accounts.
Package user provides playbooks for managing Linux user accounts.
Package ssh provides SSH connectivity utilities for remote server automation.
Package ssh provides SSH connectivity utilities for remote server automation.
Package types provides core types for SSH-based automation.
Package types provides core types for SSH-based automation.

Jump to

Keyboard shortcuts

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