Consistently use log_step for progress/status messages ("Waiting for...",
"Fetching...", "Creating...") and reserve log_info for success/completion
messages. This gives users a clear visual distinction between operations
that are still running (cyan) vs operations that have completed (green).
Also adds periodic progress updates to silent polling loops in ramnode,
cherry, and netcup IP wait functions so users see activity during long waits.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 cloud providers had identical 5-line check_ssh_key functions that
fetch SSH keys from the provider API and grep for the fingerprint.
Extract this pattern into a shared check_ssh_key_by_fingerprint helper
in shared/common.sh, reducing each cloud's function to a single line.
Affected clouds: BinaryLane, Cherry, Civo, Contabo, DigitalOcean,
Genesis Cloud, Hetzner, Hostinger, Latitude, Linode, OVH, Scaleway,
Vultr.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ssh_run_server, ssh_upload_file, ssh_interactive_session, and
ssh_verify_connectivity to shared/common.sh. These four functions
were copy-pasted identically across 21 cloud provider lib files,
differing only in SSH username (root vs ubuntu).
Providers now set SSH_USER and delegate to the shared helpers via
one-line wrappers, reducing each provider's lib by ~20 lines.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract wait-for-IP polling loops and JSON body builders from the
largest create_server() functions (ramnode 105->59, netcup 95->50,
cherry 80->57, binarylane 92->70 lines), following the pattern
already established in ionos/lib/common.sh.
Extracted helpers:
- ramnode: _ramnode_build_server_body(), _ramnode_wait_for_ip()
- netcup: _netcup_build_create_body(), _netcup_wait_for_ip()
- cherry: _cherry_wait_for_ip()
- binarylane: _binarylane_wait_for_active()
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- create_server(): Validate hostname, plan, region env vars with
validate_resource_name(); pass all values via sys.argv instead of
string interpolation in Python code
- ensure_ssh_key(): Build SSH key JSON payload with json.dumps via
sys.argv instead of raw string interpolation (prevents SSH key
content from breaking JSON)
- _cherry_json_field(), _cherry_find_key_by_fingerprint(): Use
sys.argv instead of bash variable interpolation in Python strings
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kamatera: Extract _kamatera_queue_field and _extract_kamatera_wan_ip helpers
to deduplicate inline Python blocks in wait_for_command (49->33 lines) and
get_kamatera_server_ip (49->26 lines).
Cherry: Extract _cherry_json_field, _cherry_find_key_by_fingerprint, and
_cherry_extract_primary_ip helpers to deduplicate inline Python blocks in
ensure_ssh_key (71->53 lines) and create_server.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Cherry Servers as a new cloud provider with:
- REST API-based server provisioning
- SSH key management via API
- Full root access to cloud VPS instances
- Hourly billing with no commitments
Implementation includes:
- cherry/lib/common.sh with Cherry Servers API primitives
- cherry/openclaw.sh for OpenClaw deployment
- cherry/goose.sh for Goose deployment
- cherry/README.md with authentication and usage docs
- manifest.json updates (cloud entry + 14 matrix entries)
Agent: cloud-scout
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>