Production-grade shell automation specialist - scripts, CI/CD, makefiles, deployment
Creates production-grade shell automation scripts, CI/CD pipelines, and deployment workflows.
/plugin marketplace add pluginagentmarketplace/custom-plugin-bash-shell/plugin install custom-plugin-bash-shell@pluginagentmarketplace-bash-shellsonnetExpert agent for shell-based automation, CI/CD, and deployment scripts
| Domain | Responsibility | Scope |
|---|---|---|
| Scripts | Automation scripts | bash, sh, zsh |
| CI/CD | Pipeline scripts | GitHub Actions, GitLab CI |
| Make | Build automation | Makefiles, task runners |
| Deploy | Deployment scripts | Docker, systemd, servers |
| Testing | Script testing | bats, shunit2 |
input:
type: object
properties:
operation:
type: string
enum: [create_script, create_makefile, setup_ci, deploy, test]
target:
type: string
description: Script name or project path
options:
type: object
properties:
language: { type: string, default: "bash" }
ci_platform: { type: string, enum: [github, gitlab, jenkins] }
test_framework: { type: string, enum: [bats, shunit2] }
output:
type: object
properties:
files: { type: array }
explanation: { type: string }
commands: { type: array }
#!/usr/bin/env bash
#
# script-name.sh - Brief description
# Usage: script-name.sh [options] <arguments>
#
set -euo pipefail
IFS=$'\n\t'
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
# Defaults
VERBOSE=false
DRY_RUN=false
# Logging
log_info() { printf '[INFO] %s\n' "$*" >&2; }
log_warn() { printf '[WARN] %s\n' "$*" >&2; }
log_error() { printf '[ERROR] %s\n' "$*" >&2; }
die() { log_error "$1"; exit "${2:-1}"; }
# Usage
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] <argument>
Options:
-h, --help Show this help
-v, --verbose Enable verbose output
-n, --dry-run Show what would be done
Arguments:
argument Description of argument
Examples:
$SCRIPT_NAME -v input.txt
$SCRIPT_NAME --dry-run process
EOF
}
# Parse arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--verbose) VERBOSE=true; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) break ;;
esac
done
[[ $# -ge 1 ]] || die "Missing required argument"
ARGUMENT="$1"
}
# Cleanup handler
cleanup() {
local exit_code=$?
rm -f "${TEMP_FILE:-}" 2>/dev/null
exit "$exit_code"
}
trap cleanup EXIT INT TERM
# Main function
main() {
parse_args "$@"
if [[ "$VERBOSE" == true ]]; then
log_info "Running in verbose mode"
fi
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY-RUN] Would process: $ARGUMENT"
return 0
fi
# Main logic here
log_info "Processing: $ARGUMENT"
}
main "$@"
# Project Makefile
.PHONY: all build test lint clean install deploy help
# Variables
SHELL := /bin/bash
.DEFAULT_GOAL := help
# Colors
BLUE := \033[34m
GREEN := \033[32m
RESET := \033[0m
# Help target
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "$(BLUE)%-15s$(RESET) %s\n", $$1, $$2}'
# Build targets
build: ## Build the project
@echo "$(GREEN)Building...$(RESET)"
./scripts/build.sh
test: ## Run tests
@echo "$(GREEN)Running tests...$(RESET)"
./scripts/test.sh
lint: ## Run linters
@echo "$(GREEN)Linting...$(RESET)"
shellcheck scripts/*.sh
clean: ## Clean build artifacts
@echo "$(GREEN)Cleaning...$(RESET)"
rm -rf dist/ build/
install: build ## Install dependencies
@echo "$(GREEN)Installing...$(RESET)"
./scripts/install.sh
deploy: test ## Deploy to production
@echo "$(GREEN)Deploying...$(RESET)"
./scripts/deploy.sh
# Composite targets
all: lint test build ## Run all checks and build
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
SHELL_OPTS: "-euo pipefail"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ShellCheck
run: sudo apt-get install -y shellcheck
- name: Lint shell scripts
run: |
find . -name "*.sh" -type f | xargs shellcheck
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- name: Install bats
run: |
sudo apt-get install -y bats
- name: Run tests
run: bats tests/
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Build
run: make build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
#!/usr/bin/env bash
# deploy.sh - Zero-downtime deployment script
set -euo pipefail
readonly APP_NAME="myapp"
readonly DEPLOY_DIR="/opt/$APP_NAME"
readonly BACKUP_DIR="/opt/backups/$APP_NAME"
readonly RELEASES=5
log_info() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
log_error() { printf '[ERROR] %s\n' "$*" >&2; }
# Pre-flight checks
preflight_check() {
log_info "Running pre-flight checks..."
# Check required tools
for cmd in rsync systemctl; do
command -v "$cmd" >/dev/null || {
log_error "Missing required command: $cmd"
return 1
}
done
# Check disk space
local available
available=$(df -P "$DEPLOY_DIR" | awk 'NR==2 {print $4}')
if ((available < 1048576)); then
log_error "Insufficient disk space"
return 1
fi
log_info "Pre-flight checks passed"
}
# Create backup
create_backup() {
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
local backup_path="$BACKUP_DIR/$timestamp"
log_info "Creating backup: $backup_path"
mkdir -p "$backup_path"
rsync -a "$DEPLOY_DIR/current/" "$backup_path/"
# Cleanup old backups
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
}
# Deploy new version
deploy() {
local version="$1"
local release_dir="$DEPLOY_DIR/releases/$version"
log_info "Deploying version: $version"
# Create release directory
mkdir -p "$release_dir"
# Copy files
rsync -a --delete dist/ "$release_dir/"
# Update symlink atomically
ln -sfn "$release_dir" "$DEPLOY_DIR/current.new"
mv -T "$DEPLOY_DIR/current.new" "$DEPLOY_DIR/current"
# Restart service
systemctl reload "$APP_NAME" || systemctl restart "$APP_NAME"
log_info "Deployment complete"
}
# Rollback
rollback() {
local previous
previous=$(ls -t "$DEPLOY_DIR/releases" | sed -n '2p')
if [[ -z "$previous" ]]; then
log_error "No previous release to rollback to"
return 1
fi
log_info "Rolling back to: $previous"
ln -sfn "$DEPLOY_DIR/releases/$previous" "$DEPLOY_DIR/current.new"
mv -T "$DEPLOY_DIR/current.new" "$DEPLOY_DIR/current"
systemctl restart "$APP_NAME"
}
# Main
main() {
local action="${1:-deploy}"
local version="${2:-$(git describe --tags --always)}"
case "$action" in
deploy)
preflight_check
create_backup
deploy "$version"
;;
rollback)
rollback
;;
*)
echo "Usage: $0 {deploy|rollback} [version]"
exit 1
;;
esac
}
main "$@"
#!/usr/bin/env bats
# tests/script_test.bats
setup() {
# Load script functions
source "./scripts/functions.sh"
# Create temp directory
TEST_DIR="$(mktemp -d)"
}
teardown() {
rm -rf "$TEST_DIR"
}
@test "parse_args handles --help" {
run parse_args --help
[ "$status" -eq 0 ]
[[ "$output" == *"Usage:"* ]]
}
@test "parse_args requires argument" {
run parse_args
[ "$status" -eq 1 ]
[[ "$output" == *"Missing required"* ]]
}
@test "log_info outputs to stderr" {
run log_info "test message"
[ "$status" -eq 0 ]
[[ "$output" == *"[INFO]"* ]]
}
@test "process_file handles missing file" {
run process_file "/nonexistent/file"
[ "$status" -eq 1 ]
}
@test "process_file creates output" {
echo "test content" > "$TEST_DIR/input.txt"
run process_file "$TEST_DIR/input.txt"
[ "$status" -eq 0 ]
[ -f "$TEST_DIR/output.txt" ]
}
error_patterns:
- pattern: "command not found"
cause: "Missing dependency or PATH issue"
fix: "Check if command is installed and in PATH"
- pattern: "permission denied"
cause: "Script not executable or insufficient permissions"
fix: "chmod +x script.sh or run with appropriate user"
- pattern: "syntax error"
cause: "Shell script syntax error"
fix: "Run shellcheck, check quoting and brackets"
fallback_strategy:
- level: 1
action: "Check shellcheck output"
- level: 2
action: "Run with bash -x for trace"
- level: 3
action: "Test components individually"
chmod +x script.sh#!/usr/bin/env bashshellcheck script.shScript not running?
├── Check: is it executable?
├── Check: correct shebang?
├── Check: line endings (dos2unix)?
└── Try: bash script.sh directly
Permission denied?
├── Check: file permissions
├── Check: directory permissions
├── Check: SELinux/AppArmor
└── Try: with sudo
Unexpected behavior?
├── Add: set -x for trace
├── Check: variable quoting
├── Test: with small input
└── Use: shellcheck
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.