Skill: teamtelnyx/telnyx-storage-backup (version 1.0.0)

--- name: backup-to-telnyx-storage description: Backup and restore your OpenClaw workspace to Telnyx Storage. Simple CLI-based scripts with no external dependencies. metadata: {"openclaw":{"emoji":"💾","requires":{"bins":["telnyx"],"env":["TELNYX_API_KEY"]},"primaryEnv":"TELNYX_API_KEY"}} --- # Backup to Telnyx Storage Backup and restore your OpenClaw workspace to Telnyx Storage (S3-compatible). ## Setup (One-Time) ```bash # 1. Install Telnyx CLI (if not already) npm install -g @telnyx/api-cli # 2. Authenticate telnyx auth setup ``` That's it. No boto3, no AWS credentials, no environment variables. ## Usage ### Backup ```bash ./backup.sh # Output: # 🔄 OpenClaw Backup → Telnyx Storage # ======================================== # Creating archive: openclaw-backup-20260201-120000.tar.gz # + MEMORY.md # + SOUL.md # + memory/ # ✅ Backup complete: openclaw-backup/openclaw-backup-20260201-120000.tar.gz ``` Custom bucket and workspace: ```bash ./backup.sh my-bucket ~/my-workspace ``` Control backup retention (default: 48, ~24h of 30-min backups): ```bash MAX_BACKUPS=100 ./backup.sh ``` ### List Backups ```bash ./list.sh # Output: # 📋 Available Backups # ======================================== # Bucket: openclaw-backup # # • openclaw-backup-20260201-120000.tar.gz 1.2M 2/1/2026 # • openclaw-backup-20260131-180000.tar.gz 1.1M 1/31/2026 ``` ### Restore ```bash # Restore latest backup ./restore.sh latest # Restore specific backup ./restore.sh openclaw-backup-20260201-120000.tar.gz # Restore to different location ./restore.sh latest my-bucket ~/restored-workspace ``` ## What Gets Backed Up - `AGENTS.md`, `SOUL.md`, `USER.md`, `IDENTITY.md`, `TOOLS.md` - `MEMORY.md`, `HEARTBEAT.md`, `GUARDRAILS.md` - `memory/`, `knowledge/`, `scripts/` ## Scheduling Automatic backups every 30 minutes: ```bash crontab -e # Add: */30 * * * * ~/skills/backup-to-telnyx-storage/backup.sh >> /tmp/backup.log 2>&1 ``` ## Pricing Telnyx Storage: **$0.023/GB/month** — typical workspace costs pennies. ## Legacy Python Script The original `backup.py` using boto3 is still available if you need AWS SDK compatibility: ```bash pip install boto3 export TELNYX_API_KEY=KEYxxxxx python3 backup.py ``` Note: The CLI-based scripts (`backup.sh`, `list.sh`, `restore.sh`) are recommended as they require no additional dependencies and provide full backup/list/restore functionality.

path: backup.py
content: #!/usr/bin/env python3
"""
Backup OpenClaw workspace to Telnyx Storage (S3-compatible).

Creates a compressed archive of the workspace and uploads it to Telnyx Storage.

Setup:
1. Set TELNYX_API_KEY in your environment or a .env file
2. Run: python3 backup.py
3. Schedule via cron for automated backups

Requirements:
- Python 3.8+
- boto3: pip install boto3
"""

import os
import sys
import tarfile
import tempfile
from datetime import datetime
from pathlib import Path

try:
    import boto3
    from botocore.config import Config
except ImportError:
    print("Error: boto3 not installed. Run: pip install boto3")
    sys.exit(1)


# Configuration (override via environment variables)
BUCKET = os.environ.get("BACKUP_BUCKET", "openclaw-backup")
REGION = os.environ.get("BACKUP_REGION", "us-central-1")
ENDPOINT = os.environ.get("TELNYX_STORAGE_ENDPOINT", "https://us-central-1.telnyxcloudstorage.com")
WORKSPACE = Path(os.environ.get("OPENCLAW_WORKSPACE", os.environ.get("CLAWDBOT_WORKSPACE", Path.home() / "clawd")))
MAX_BACKUPS = int(os.environ.get("MAX_BACKUPS", "48"))  # Keep ~24h of 30-min backups

# What to back up — customize as needed
FILES_TO_BACKUP = [
    "AGENTS.md",
    "SOUL.md",
    "USER.md",
    "IDENTITY.md",
    "TOOLS.md",
    "MEMORY.md",
    "HEARTBEAT.md",
    "GUARDRAILS.md",
    "STATE.md",
    "memory",
    "knowledge",
    "scripts",
    "skills",
]


def load_api_key() -> str:
    """Load Telnyx API key from environment or .env file."""
    # 1. Check environment variable
    key = os.environ.get("TELNYX_API_KEY")
    if key:
        return key

    # 2. Check .env files in workspace
    for env_path in [WORKSPACE / ".env", WORKSPACE / ".env.telnyx",
                     Path(__file__).parent / ".env"]:
        if env_path.exists():
            for line in env_path.read_text().splitlines():
                line = line.strip()
                if line.startswith("TELNYX_API_KEY="):
                    return line.split("=", 1)[1].strip().strip('"').strip("'")

    print("Error: TELNYX_API_KEY not found.")
    print("Set it as an environment variable or add it to a .env file:")
    print("  export TELNYX_API_KEY=KEYxxxxx")
    print("  # or create .env with: TELNYX_API_KEY=KEYxxxxx")
    sys.exit(1)


def create_archive(workspace: Path, files: list) -> Path:
    """Create a tar.gz archive of specified files/directories."""
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    archive_name = f"openclaw-backup-{timestamp}.tar.gz"
    archive_path = Path(tempfile.gettempdir()) / archive_name

    print(f"Creating archive: {archive_name}")

    with tarfile.open(archive_path, "w:gz") as tar:
        for item in files:
            full_path = workspace / item
            if full_path.exists():
                print(f"  + {item}")
                tar.add(full_path, arcname=item)
            else:
                print(f"  - {item} (skipping)")

    size_kb = archive_path.stat().st_size / 1024
    print(f"Archive size: {size_kb:.1f} KB")
    return archive_path


def get_s3_client(api_key: str):
    """Create S3 client for Telnyx Storage."""
    return boto3.client(
        "s3",
        endpoint_url=ENDPOINT,
        region_name=REGION,
        aws_access_key_id=api_key,
        aws_secret_access_key=api_key,  # Telnyx uses the API key for both
        config=Config(signature_version="s3v4"),
    )


def ensure_bucket(s3, bucket: str):
    """Create bucket if it doesn't exist."""
    try:
        s3.head_bucket(Bucket=bucket)
    except Exception:
        print(f"Creating bucket: {bucket}")
        try:
            s3.create_bucket(Bucket=bucket)
        except Exception as e:
            if "BucketAlreadyOwnedByYou" not in str(e):
                raise


def cleanup_old_backups(s3, bucket: str, max_keep: int):
    """Remove old backups beyond the retention limit."""
    try:
        response = s3.list_objects_v2(Bucket=bucket, Prefix="openclaw-backup-")
        objects = response.get("Contents", [])
        if len(objects) <= max_keep:
            return

        # Sort by date, oldest first
        objects.sort(key=lambda x: x["LastModified"])
        to_delete = objects[:len(objects) - max_keep]

        for obj in to_delete:
            s3.delete_object(Bucket=bucket, Key=obj["Key"])
            print(f"  Removed: {obj['Key']}")

        print(f"  Cleaned up {len(to_delete)} old backup(s)")
    except Exception as e:
        print(f"  Warning: cleanup failed: {e}")


def main():
    [REDACTED_SECRET])

    print("OpenClaw Backup -> Telnyx Storage")
    print("=" * 40)

    # Create archive
    archive_path = create_archive(WORKSPACE, FILES_TO_BACKUP)

    try:
        # Upload
        s3 = get_s3_client(api_key)
        ensure_bucket(s3, BUCKET)

        key = archive_path.name
        print(f"Uploading to: s3://{BUCKET}/{key}")
        s3.upload_file(str(archive_path), BUCKET, key)
        print(f"Done: s3://{BUCKET}/{key}")

        # Cleanup old backups
        cleanup_old_backups(s3, BUCKET, MAX_BACKUPS)

    finally:
        archive_path.unlink(missing_ok=True)


if __name__ == "__main__":
    main()


path: backup.sh
content: #!/bin/bash
#
# backup.sh - Backup OpenClaw workspace to Telnyx Storage using CLI
#
# This replaces the Python boto3 approach with the simpler Telnyx CLI.
# Setup: telnyx auth setup (one-time)
#
# Usage: ./backup.sh [bucket_name] [workspace_path]
#
# Environment:
#   MAX_BACKUPS - Number of backups to keep (default: 48, ~24h of 30-min backups)
#

set -e

# Defaults
BUCKET="${1:-openclaw-backup}"
WORKSPACE="${2:-$HOME/clawd}"
MAX_BACKUPS="${MAX_BACKUPS:-48}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ARCHIVE_NAME="openclaw-backup-${TIMESTAMP}.tar.gz"
TEMP_ARCHIVE="/tmp/${ARCHIVE_NAME}"

# Files to backup
FILES_TO_BACKUP=(
    "AGENTS.md"
    "SOUL.md"
    "USER.md"
    "IDENTITY.md"
    "TOOLS.md"
    "MEMORY.md"
    "HEARTBEAT.md"
    "GUARDRAILS.md"
    "INTEGRATIONS.md"
    "memory"
    "knowledge"
    "scripts"
)

echo "🔄 OpenClaw Backup → Telnyx Storage"
echo "========================================"

# Check CLI is available
if ! command -v telnyx &> /dev/null; then
    echo "❌ Telnyx CLI not found. Install: npm install -g telnyx-cli"
    echo "   Then run: telnyx auth setup"
    exit 1
fi

# Check workspace exists
if [ ! -d "$WORKSPACE" ]; then
    echo "❌ Workspace not found: $WORKSPACE"
    exit 1
fi

# Build list of existing files to archive
cd "$WORKSPACE"
EXISTING_FILES=()
echo "Creating archive: ${ARCHIVE_NAME}"
for f in "${FILES_TO_BACKUP[@]}"; do
    if [ -e "$f" ]; then
        echo "  + $f"
        EXISTING_FILES+=("$f")
    else
        echo "  - $f (not found, skipping)"
    fi
done

# Create archive
if [ ${#EXISTING_FILES[@]} -eq 0 ]; then
    echo "❌ No files to backup!"
    exit 1
fi

tar -czf "$TEMP_ARCHIVE" "${EXISTING_FILES[@]}"
SIZE=$(du -h "$TEMP_ARCHIVE" | cut -f1)
echo "Archive size: $SIZE"

# Ensure bucket exists (create if needed, ignore if already exists)
echo "Checking bucket: $BUCKET"
if ! telnyx storage bucket list 2>/dev/null | grep -qw "$BUCKET"; then
    echo "Creating bucket: $BUCKET"
    if ! telnyx storage bucket create "$BUCKET" 2>&1; then
        # Bucket might already exist (race condition or list didn't show it)
        # Verify we can access it
        if ! telnyx storage bucket list 2>/dev/null | grep -qw "$BUCKET"; then
            echo "❌ Failed to create or access bucket: $BUCKET"
            exit 1
        fi
        echo "Bucket already exists, continuing..."
    fi
else
    echo "Bucket exists: $BUCKET"
fi

# Upload
echo "Uploading to: telnyx://${BUCKET}/${ARCHIVE_NAME}"
telnyx storage object put "$BUCKET" "$TEMP_ARCHIVE" --key "$ARCHIVE_NAME"

# Cleanup temp file
rm -f "$TEMP_ARCHIVE"

echo "✅ Backup complete: telnyx://${BUCKET}/${ARCHIVE_NAME}"

# Prune old backups (keep MAX_BACKUPS most recent)
echo ""
echo "Checking for old backups to prune (keeping $MAX_BACKUPS)..."
BACKUP_LIST=$(telnyx storage object list "$BUCKET" 2>/dev/null | grep "openclaw-backup-" | awk '{print $1}' | sort)
BACKUP_COUNT=$(echo "$BACKUP_LIST" | grep -c "openclaw-backup-" || echo 0)

if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
    DELETE_COUNT=$((BACKUP_COUNT - MAX_BACKUPS))
    echo "  Found $BACKUP_COUNT backups, pruning $DELETE_COUNT old backup(s)..."
    
    # Get oldest backups to delete (sorted oldest first, take first N)
    TO_DELETE=$(echo "$BACKUP_LIST" | head -n "$DELETE_COUNT")
    
    for OLD_BACKUP in $TO_DELETE; do
        if telnyx storage object delete "$BUCKET" "$OLD_BACKUP" --force 2>/dev/null; then
            echo "  🗑️  Deleted: $OLD_BACKUP"
        fi
    done
    
    echo "  Cleanup complete."
else
    echo "  $BACKUP_COUNT backup(s) found, no pruning needed."
fi


path: list.sh
content: #!/bin/bash
#
# list.sh - List available backups in Telnyx Storage
#
# Usage: ./list.sh [bucket_name]
#

BUCKET="${1:-openclaw-backup}"

echo "📋 Available Backups"
echo "========================================"
echo "Bucket: $BUCKET"
echo ""

# Check CLI is available
if ! command -v telnyx &> /dev/null; then
    echo "❌ Telnyx CLI not found. Install: npm install -g telnyx-cli"
    echo "   Then run: telnyx auth setup"
    exit 1
fi

telnyx storage object list "$BUCKET" | grep "openclaw-backup-" | while read -r backup; do
    echo "  • $backup"
done

echo ""
echo "To restore: ./restore.sh <backup_name>"
echo "To restore latest: ./restore.sh latest"


path: restore.sh
content: #!/bin/bash
#
# restore.sh - Restore OpenClaw workspace from Telnyx Storage
#
# Usage: ./restore.sh <backup_name> [bucket_name] [workspace_path]
#
# Examples:
#   ./restore.sh openclaw-backup-20260130-120000.tar.gz
#   ./restore.sh latest  # Restores most recent backup
#

set -e

BACKUP_NAME="${1:-}"
BUCKET="${2:-openclaw-backup}"
WORKSPACE="${3:-$HOME/clawd}"
TEMP_DIR="/tmp/openclaw-restore-$$"

echo "🔄 OpenClaw Restore ← Telnyx Storage"
echo "========================================"

# Check CLI is available
if ! command -v telnyx &> /dev/null; then
    echo "❌ Telnyx CLI not found. Install: npm install -g telnyx-cli"
    echo "   Then run: telnyx auth setup"
    exit 1
fi

# If no backup specified or "latest", find most recent
if [ -z "$BACKUP_NAME" ] || [ "$BACKUP_NAME" = "latest" ]; then
    echo "Finding latest backup in bucket: $BUCKET"
    # Extract just the filename (first column) from the listing
    BACKUP_NAME=$(telnyx storage object list "$BUCKET" 2>/dev/null | grep "openclaw-backup-" | awk '{print $1}' | sort | tail -1)
    if [ -z "$BACKUP_NAME" ]; then
        echo "❌ No backups found in bucket: $BUCKET"
        exit 1
    fi
    echo "Latest backup: $BACKUP_NAME"
fi

# Download
mkdir -p "$TEMP_DIR"
TEMP_ARCHIVE="$TEMP_DIR/$BACKUP_NAME"

echo "Downloading: telnyx://${BUCKET}/${BACKUP_NAME}"
telnyx storage object get "$BUCKET" "$BACKUP_NAME" -o "$TEMP_ARCHIVE"

# Confirm restore
echo ""
echo "⚠️  This will overwrite files in: $WORKSPACE"
read -p "Continue? [y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Cancelled."
    rm -rf "$TEMP_DIR"
    exit 0
fi

# Extract
echo "Extracting to: $WORKSPACE"
mkdir -p "$WORKSPACE"
tar -xzf "$TEMP_ARCHIVE" -C "$WORKSPACE"

# Cleanup
rm -rf "$TEMP_DIR"

echo "✅ Restore complete from: $BACKUP_NAME"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)