Skip to content

foundercatalyst/simple_infrastructure

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple Infrastructure

A migration-like DSL for server provisioning via SSH. Provides a change-based approach to server configuration, similar to how Rails database migrations work but for infrastructure.

Features

  • Idempotency: Changes can be run multiple times safely
  • Auditability: All changes are tracked in version control
  • Consistency: All servers in an environment receive the same configuration
  • Incremental updates: Only pending changes are applied
  • Change tracking: State stored on each server, automatically re-applied on rebuild

Installation

Add to your Gemfile:

gem "simple_infrastructure"

Then run:

bundle install
rails generate simple_infrastructure:install

This creates:

  • bin/infrastructure — CLI tool
  • config/infrastructure/inventory.yml — server inventory
  • config/infrastructure/changes/ — directory for change files

For Rails apps, the gem auto-configures via Railtie (sets project_root to Rails.root and logger to Rails.logger).

For standalone use:

require "simple_infrastructure"

SimpleInfrastructure.configure do |config|
  config.project_root = "/path/to/project"
end

Directory Structure

config/infrastructure/
├── inventory.yml          # Server inventory
└── changes/               # Change files
    ├── 20250121000100_install_essentials.rb
    ├── 20250121000200_configure_storage.rb
    └── ...

Server Inventory

Servers are defined in config/infrastructure/inventory.yml, grouped by environment:

defaults:
  user: civo

servers:
  production:
    - hostname: web1.production.example.com
    - hostname: web2.production.example.com
    - hostname: db.production.example.com

  staging:
    - hostname: app1.staging.example.com

The defaults section provides default values for all servers. Each server entry can override these defaults.

CLI Usage

Check Status

View change status for all servers:

bin/infrastructure status

Output shows which servers have pending changes:

Production -------------------------------------------------------------
  ✗ web1.production.example.com (6 pending)
  ✔︎ web2.production.example.com (up to date)

Staging ----------------------------------------------------------------
  ✗ app1.staging.example.com (2 pending)

Use -v or --verbose to see the list of pending changes:

bin/infrastructure status -v

Dry Run

Preview what changes would be made without executing them:

bin/infrastructure --dry-run staging
bin/infrastructure --dry-run production
bin/infrastructure --dry-run web1.production.example.com

Run Changes

Apply pending changes:

# All servers in an environment
bin/infrastructure staging
bin/infrastructure production

# Specific server
bin/infrastructure web1.production.example.com

Generate New Change

Create a new change file:

bin/infrastructure new setup_redis

# Or using the Rails generator:
rails generate simple_infrastructure:change setup_redis

This creates a timestamped file like config/infrastructure/changes/20250121143000_setup_redis.rb.

Writing Changes

Basic Structure

# Target specific servers (omit target line for all servers)
target env: :production                    # All production servers
target env: :staging                       # All staging servers
target hostname: "db.production.example.com"  # Specific server

# Run shell commands
run "apt update", sudo: true
run "systemctl restart nginx", sudo: true

# Manage file contents
file "/etc/ssh/sshd_config", sudo: true do
  contains "PermitRootLogin no"           # Ensure line exists
  remove "PermitRootLogin yes"            # Remove line if present
  on_change { run "systemctl restart sshd", sudo: true }
end

# Manage YAML files
yaml "/etc/config.yml", sudo: true do
  set "server.port", 8080
  remove "deprecated.setting"
end

# Manage TOML files
toml "/etc/config.toml", sudo: true do
  set "database.host", "localhost"
end

# Upload local files
upload "config/backup/script.sh", "/root/bin/script.sh", sudo: true, mode: "700"

Targeting Servers

By default, changes apply to all servers. Use target to restrict to specific servers:

# All servers in production
target env: :production

# Specific hostname
target hostname: "db.production.example.com"

# Hostname pattern (regex)
target hostname: /^web\d+\.production\./

DSL Reference

run(command, sudo: false)

Execute a shell command on the remote server.

run "apt update", sudo: true
run "docker compose up -d"

file(path, sudo: false, &block)

Manage plain text file contents.

file "/etc/fstab", sudo: true do
  contains "/swapfile none swap sw 0 0"  # Add if missing
  remove "/old/swap none swap sw 0 0"    # Remove if present
  on_change { run "mount -a", sudo: true }  # Run only if file changed
end

yaml(path, sudo: false, &block)

Manage YAML configuration files.

yaml "/etc/app/config.yml", sudo: true do
  set "database.host", "localhost"
  set "database.port", 3306
  remove "deprecated_key"
end

toml(path, sudo: false, &block)

Manage TOML configuration files.

toml "/etc/app/config.toml", sudo: true do
  set "server.bind", "0.0.0.0"
  set "server.port", 8080
end

upload(local_path, remote_path, sudo: false, mode: nil)

Upload a local file to the remote server.

upload "bin/backup", "/root/bin/backup", sudo: true, mode: "700"
upload "config/nginx.conf", "/etc/nginx/nginx.conf", sudo: true

The on_change Callback

The file DSL supports an on_change callback that only executes when the file was actually modified:

file "/etc/ssh/sshd_config", sudo: true do
  remove "PermitRootLogin yes"
  contains "PermitRootLogin no"
  on_change { run "systemctl restart sshd", sudo: true }
end

This is useful for restarting services only when their configuration changes, avoiding unnecessary service restarts.

Rake Tasks

Alternative to the CLI:

# Show status
rake infrastructure:status

# Run changes
rake infrastructure:run[production]
rake infrastructure:run[staging]

# Dry run
rake infrastructure:dry_run[production]

# Generate change
rake infrastructure:generate[setup_redis]

Change Tracking

The system tracks which changes have been applied by storing log files on each server in ~/.infrastructure/:

~/.infrastructure/
├── 20250121000100_install_essentials.log
├── 20250121000200_configure_storage.log
├── 20250121000300_configure_swap.log
└── ...

Each log file contains the timestamp when the change was applied and any output from the commands.

This approach means:

  • State lives on the server itself, not in your local repository
  • If a server is deleted and recreated, changes will be re-applied automatically
  • Log files provide debugging information if something goes wrong

Best Practices

  1. Make changes idempotent: Use contains instead of blindly appending, check if files exist before creating them
  2. Use on_change for service restarts: Avoid unnecessary restarts by only restarting when config actually changes
  3. Test with dry-run first: Always preview changes before applying to production
  4. Target narrowly when appropriate: Use specific hostnames for server-specific configuration (like database backups)
  5. Keep changes small and focused: One concern per change makes troubleshooting easier

License

MIT License. See LICENSE.txt.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages