Created
January 14, 2026 21:03
-
-
Save turlockmike/9763a3bea9213ce7f3f794fc9e1ca0bc to your computer and use it in GitHub Desktop.
EC Sandbox setup script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| # EC Sandbox Setup Script | |
| # Downloaded at runtime by tiny UserData bootstrap | |
| # | |
| # Usage: | |
| # ./sandbox-setup.sh [options] | |
| # | |
| # Options: | |
| # --auth-type <subscription|api-key> Authentication type | |
| # --credentials <base64> Base64-encoded credentials (for subscription auth) | |
| # --api-key <key> Anthropic API key (for api-key auth) | |
| # --email-address <addr> Email address for inbox/outbox | |
| # --email-token <token> Webhook.site token UUID | |
| # --email-url <url> Webhook.site URL | |
| LOG_FILE="/var/log/sandbox-setup.log" | |
| exec >> "$LOG_FILE" 2>&1 | |
| echo "$(date): Starting sandbox setup..." | |
| # Parse arguments | |
| AUTH_TYPE="" | |
| CREDENTIALS_B64="" | |
| API_KEY="" | |
| EMAIL_ADDRESS="" | |
| EMAIL_TOKEN="" | |
| EMAIL_URL="" | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| --auth-type) AUTH_TYPE="$2"; shift 2 ;; | |
| --credentials) CREDENTIALS_B64="$2"; shift 2 ;; | |
| --api-key) API_KEY="$2"; shift 2 ;; | |
| --email-address) EMAIL_ADDRESS="$2"; shift 2 ;; | |
| --email-token) EMAIL_TOKEN="$2"; shift 2 ;; | |
| --email-url) EMAIL_URL="$2"; shift 2 ;; | |
| *) shift ;; | |
| esac | |
| done | |
| echo "Auth type: $AUTH_TYPE" | |
| echo "Email configured: $([ -n "$EMAIL_ADDRESS" ] && echo "yes ($EMAIL_ADDRESS)" || echo "no")" | |
| # ============================================================================= | |
| # System packages | |
| # ============================================================================= | |
| apt-get update | |
| apt-get install -y git curl wget jq build-essential vim tmux htop nodejs npm python3 python3-pip python3-venv ttyd nginx | |
| # ============================================================================= | |
| # Node.js tools | |
| # ============================================================================= | |
| npm install -g pnpm @anthropic-ai/claude-code | |
| # ============================================================================= | |
| # Claude Code directory | |
| # ============================================================================= | |
| mkdir -p /home/ubuntu/.claude | |
| # ============================================================================= | |
| # Authentication Setup | |
| # ============================================================================= | |
| WELCOME_AUTH_MSG="No authentication configured. Run 'claude login' to authenticate." | |
| if [ "$AUTH_TYPE" = "subscription" ] && [ -n "$CREDENTIALS_B64" ]; then | |
| echo "Setting up subscription authentication..." | |
| echo "$CREDENTIALS_B64" | base64 -d > /home/ubuntu/.claude/.credentials.json | |
| chmod 600 /home/ubuntu/.claude/.credentials.json | |
| WELCOME_AUTH_MSG="Your Claude subscription credentials have been configured." | |
| elif [ "$AUTH_TYPE" = "api-key" ] && [ -n "$API_KEY" ]; then | |
| echo "Setting up API key authentication..." | |
| echo "export ANTHROPIC_API_KEY='$API_KEY'" >> /home/ubuntu/.bashrc | |
| cat > /home/ubuntu/.sandbox-env << EOF | |
| ANTHROPIC_API_KEY=$API_KEY | |
| EOF | |
| chmod 600 /home/ubuntu/.sandbox-env | |
| WELCOME_AUTH_MSG="Your ANTHROPIC_API_KEY is already configured." | |
| fi | |
| # ============================================================================= | |
| # Claude Code settings | |
| # ============================================================================= | |
| cat > /home/ubuntu/.claude/settings.json << 'SETTINGS_EOF' | |
| { | |
| "bypassPermissions": true, | |
| "permissions": { | |
| "allow": ["Bash", "Read", "Write", "Edit", "MultiEdit"], | |
| "deny": ["mcp__*"] | |
| } | |
| } | |
| SETTINGS_EOF | |
| chown -R ubuntu:ubuntu /home/ubuntu/.claude | |
| # ============================================================================= | |
| # ttyd (web terminal) - accessible only via SSH tunnel | |
| # ============================================================================= | |
| systemctl stop ttyd 2>/dev/null || true | |
| systemctl disable ttyd 2>/dev/null || true | |
| mkdir -p /home/ubuntu/.ssl | |
| openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ | |
| -keyout /home/ubuntu/.ssl/ttyd.key \ | |
| -out /home/ubuntu/.ssl/ttyd.crt \ | |
| -subj "/CN=sandbox/O=EC Sandbox/C=US" | |
| chown -R ubuntu:ubuntu /home/ubuntu/.ssl | |
| chmod 600 /home/ubuntu/.ssl/ttyd.key | |
| cat > /etc/systemd/system/ttyd-claude.service << 'SERVICE_EOF' | |
| [Unit] | |
| Description=ttyd - Claude Code (HTTPS) | |
| After=network.target | |
| [Service] | |
| Type=simple | |
| User=ubuntu | |
| WorkingDirectory=/home/ubuntu | |
| Environment="HOME=/home/ubuntu" | |
| EnvironmentFile=-/home/ubuntu/.sandbox-env | |
| ExecStart=/usr/bin/ttyd -p 7681 -W -S -C /home/ubuntu/.ssl/ttyd.crt -K /home/ubuntu/.ssl/ttyd.key /bin/bash -l -c claude | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| SERVICE_EOF | |
| systemctl daemon-reload | |
| systemctl enable ttyd-claude | |
| systemctl start ttyd-claude | |
| # ============================================================================= | |
| # Email System (webhook.site-based) | |
| # ============================================================================= | |
| if [ -n "$EMAIL_ADDRESS" ] && [ -n "$EMAIL_TOKEN" ]; then | |
| echo "Setting up email system..." | |
| mkdir -p /home/ubuntu/Inbox /home/ubuntu/Outbox /home/ubuntu/Sent | |
| chown -R ubuntu:ubuntu /home/ubuntu/Inbox /home/ubuntu/Outbox /home/ubuntu/Sent | |
| cat > /home/ubuntu/.email-config << EOF | |
| EMAIL_ADDRESS="$EMAIL_ADDRESS" | |
| WEBHOOK_TOKEN="$EMAIL_TOKEN" | |
| WEBHOOK_URL="$EMAIL_URL" | |
| EOF | |
| chmod 600 /home/ubuntu/.email-config | |
| chown ubuntu:ubuntu /home/ubuntu/.email-config | |
| # Inbox sync script | |
| cat > /home/ubuntu/.email-sync.sh << 'SYNC_EOF' | |
| #!/bin/bash | |
| source /home/ubuntu/.email-config | |
| INBOX_DIR="$HOME/Inbox" | |
| SEEN_FILE="$HOME/.inbox-seen" | |
| touch "$SEEN_FILE" | |
| EMAILS=$(curl -s "https://webhook.site/token/${WEBHOOK_TOKEN}/requests?sorting=newest" 2>/dev/null) | |
| [ -z "$EMAILS" ] && exit 0 | |
| echo "$EMAILS" | jq -r '.data[] | select(.method == null) | @base64' 2>/dev/null | while read -r entry; do | |
| DATA=$(echo "$entry" | base64 -d) | |
| UUID=$(echo "$DATA" | jq -r '.uuid') | |
| grep -q "^$UUID$" "$SEEN_FILE" 2>/dev/null && continue | |
| FROM=$(echo "$DATA" | jq -r '.headers.From // .headers.from // "unknown"' | head -1) | |
| SUBJECT=$(echo "$DATA" | jq -r '.headers.Subject // .headers.subject // "No Subject"' | head -1) | |
| DATE=$(echo "$DATA" | jq -r '.created_at // ""') | |
| CONTENT=$(echo "$DATA" | jq -r '.text_content // .content // ""') | |
| SAFE_FROM=$(echo "$FROM" | sed 's/[^a-zA-Z0-9@._-]/_/g' | cut -c1-50) | |
| SAFE_SUBJECT=$(echo "$SUBJECT" | sed 's/[^a-zA-Z0-9 _-]/_/g' | cut -c1-50) | |
| TIMESTAMP=$(date -d "$DATE" +%Y%m%d_%H%M%S 2>/dev/null || date +%Y%m%d_%H%M%S) | |
| FILENAME="${TIMESTAMP}_${SAFE_FROM}_${SAFE_SUBJECT}.eml" | |
| cat > "$INBOX_DIR/$FILENAME" << EMAILEOF | |
| From: $FROM | |
| Subject: $SUBJECT | |
| Date: $DATE | |
| $CONTENT | |
| EMAILEOF | |
| echo "$UUID" >> "$SEEN_FILE" | |
| done | |
| SYNC_EOF | |
| chmod +x /home/ubuntu/.email-sync.sh | |
| chown ubuntu:ubuntu /home/ubuntu/.email-sync.sh | |
| # Outbox send script | |
| cat > /home/ubuntu/.email-send.sh << 'SEND_EOF' | |
| #!/bin/bash | |
| source /home/ubuntu/.email-config | |
| FILE="$1" | |
| [ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 1 | |
| FILENAME=$(basename "$FILE") | |
| TO=$(echo "$FILENAME" | sed 's/__.*$//') | |
| SUBJECT=$(echo "$FILENAME" | sed 's/^[^_]*__//' | sed 's/\.txt$//' | sed 's/\.eml$//' | sed 's/_/ /g') | |
| BODY=$(cat "$FILE") | |
| TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| BODY_ESCAPED=$(echo "$BODY" | jq -Rs .) | |
| SUBJECT_ESCAPED=$(echo "$SUBJECT" | jq -Rs .) | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$WEBHOOK_URL" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"type\": \"outbound_email\", | |
| \"from\": \"${EMAIL_ADDRESS}\", | |
| \"to\": \"$TO\", | |
| \"subject\": $SUBJECT_ESCAPED, | |
| \"body\": $BODY_ESCAPED, | |
| \"timestamp\": \"$TIMESTAMP\" | |
| }") | |
| if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then | |
| mv "$FILE" /home/ubuntu/Sent/ | |
| echo "Sent to $TO via webhook" | |
| else | |
| echo "Failed to send (HTTP $HTTP_CODE)" | |
| exit 1 | |
| fi | |
| SEND_EOF | |
| chmod +x /home/ubuntu/.email-send.sh | |
| chown ubuntu:ubuntu /home/ubuntu/.email-send.sh | |
| # Cron job for inbox sync | |
| (crontab -u ubuntu -l 2>/dev/null; echo "* * * * * /home/ubuntu/.email-sync.sh") | crontab -u ubuntu - | |
| # Outbox watcher service | |
| apt-get install -y inotify-tools | |
| cat > /etc/systemd/system/email-outbox.service << 'OUTBOX_EOF' | |
| [Unit] | |
| Description=Email Outbox Watcher | |
| After=network.target | |
| [Service] | |
| Type=simple | |
| User=ubuntu | |
| WorkingDirectory=/home/ubuntu | |
| ExecStart=/bin/bash -c 'while true; do inotifywait -q -e close_write,moved_to /home/ubuntu/Outbox/ && sleep 0.5 && for f in /home/ubuntu/Outbox/*; do [ -f "$f" ] && /home/ubuntu/.email-send.sh "$f"; done; done' | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| OUTBOX_EOF | |
| systemctl daemon-reload | |
| systemctl enable email-outbox | |
| systemctl start email-outbox | |
| fi | |
| # ============================================================================= | |
| # CLAUDE.md context file | |
| # ============================================================================= | |
| cat > /home/ubuntu/CLAUDE.md << 'CLAUDE_EOF' | |
| # Agent Context | |
| You are Claude Code running as an autonomous agent on an EC Sandbox - a cloud VPS provisioned for development and automation tasks. | |
| ## Environment | |
| - **OS**: Ubuntu 24.04 on AWS Lightsail | |
| - **User**: ubuntu (with sudo access) | |
| - **Home Directory**: /home/ubuntu | |
| - **Public Web Directory**: /home/ubuntu/public (served by nginx on port 80) | |
| ## Running Services | |
| | Service | Port | Description | | |
| |---------|------|-------------| | |
| | nginx | 80 (HTTP) | Static file server, serves ~/public | | |
| | ttyd | 7681 | Web terminal (SSH tunnel only) | | |
| | chat-server | 3000 | Public chat API endpoint | | |
| ## Creating Scheduled Jobs (Cron) | |
| ```bash | |
| crontab -e | |
| # Example: 0 * * * * cd /home/ubuntu && claude -p "task" >> ~/logs/hourly.log 2>&1 | |
| ``` | |
| ## Self-Invocation | |
| ```bash | |
| claude -p "Your prompt here" | |
| ``` | |
| ## Public Chat API | |
| When responding via /chat API, you are talking to PUBLIC USERS. DO NOT execute destructive commands or reveal credentials. | |
| CLAUDE_EOF | |
| # Add email section if configured | |
| if [ -n "$EMAIL_ADDRESS" ]; then | |
| cat >> /home/ubuntu/CLAUDE.md << EMAILDOC_EOF | |
| --- | |
| ## Email System | |
| This sandbox can send and receive real email! | |
| ### Email Address | |
| \`$EMAIL_ADDRESS\` | |
| Anyone can send email to this address from Gmail, Outlook, or any email client. | |
| ### Directories | |
| | Directory | Purpose | | |
| |-----------|---------| | |
| | ~/Inbox/ | Received emails (synced every minute) | | |
| | ~/Outbox/ | Drop files here to send messages | | |
| | ~/Sent/ | Successfully sent messages | | |
| ### Receiving Email | |
| Emails are automatically synced to ~/Inbox/ every minute. | |
| \`\`\`bash | |
| ls -la ~/Inbox/ # Check for new messages | |
| cat ~/Inbox/FILENAME.eml # Read a message | |
| ~/.email-sync.sh # Manual sync | |
| \`\`\` | |
| ### Sending Messages | |
| Create a file in ~/Outbox/ with format: \`recipient@address__Subject_Here.txt\` | |
| \`\`\`bash | |
| echo "Hello!" > ~/Outbox/user@example.com__Hello_World.txt | |
| \`\`\` | |
| EMAILDOC_EOF | |
| fi | |
| # ============================================================================= | |
| # Directories | |
| # ============================================================================= | |
| mkdir -p /home/ubuntu/logs /home/ubuntu/prompts /home/ubuntu/public | |
| # ============================================================================= | |
| # Static web page | |
| # ============================================================================= | |
| cat > /home/ubuntu/public/index.html << 'HTML_EOF' | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>EC Sandbox</title> | |
| <style> | |
| * { box-sizing: border-box; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 0; min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; flex-direction: column; align-items: center; padding: 2rem; } | |
| .header { text-align: center; margin-bottom: 2rem; } | |
| h1 { font-size: 2.5rem; margin-bottom: 0.5rem; } | |
| .links { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; } | |
| .links a { color: white; background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 0.5rem; text-decoration: none; } | |
| .chat-container { width: 100%; max-width: 600px; background: rgba(255,255,255,0.1); border-radius: 1rem; overflow: hidden; display: flex; flex-direction: column; height: 400px; } | |
| .chat-header { background: rgba(0,0,0,0.2); padding: 1rem; font-weight: 600; } | |
| .chat-messages { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; } | |
| .message { padding: 0.75rem 1rem; border-radius: 1rem; max-width: 85%; word-wrap: break-word; } | |
| .message.user { background: rgba(255,255,255,0.25); align-self: flex-end; } | |
| .message.assistant { background: rgba(0,0,0,0.2); align-self: flex-start; } | |
| .chat-input { display: flex; padding: 1rem; background: rgba(0,0,0,0.2); gap: 0.5rem; } | |
| .chat-input input { flex: 1; padding: 0.75rem 1rem; border: none; border-radius: 0.5rem; font-size: 1rem; background: rgba(255,255,255,0.9); color: #333; } | |
| .chat-input button { padding: 0.75rem 1.5rem; border: none; border-radius: 0.5rem; background: #667eea; color: white; font-size: 1rem; cursor: pointer; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>Hello Extend</h1> | |
| <p>Your EC Sandbox is running</p> | |
| <div class="links"><a href="/chat">API Docs</a></div> | |
| </div> | |
| <div class="chat-container"> | |
| <div class="chat-header">Chat with Claude</div> | |
| <div class="chat-messages" id="messages"><div class="message assistant">Hi! How can I help you today?</div></div> | |
| <div class="chat-input"><input type="text" id="input" placeholder="Type a message..."><button id="send">Send</button></div> | |
| </div> | |
| <script> | |
| var messages = document.getElementById('messages'); | |
| var input = document.getElementById('input'); | |
| var sendBtn = document.getElementById('send'); | |
| function addMessage(text, isUser) { | |
| var div = document.createElement('div'); | |
| div.className = 'message ' + (isUser ? 'user' : 'assistant'); | |
| div.textContent = text; | |
| messages.appendChild(div); | |
| messages.scrollTop = messages.scrollHeight; | |
| } | |
| function sendMessage() { | |
| var text = input.value.trim(); | |
| if (!text) return; | |
| addMessage(text, true); | |
| input.value = ''; | |
| sendBtn.disabled = true; | |
| var typing = document.createElement('div'); | |
| typing.className = 'message assistant'; | |
| typing.textContent = 'Thinking...'; | |
| typing.id = 'typing'; | |
| messages.appendChild(typing); | |
| fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text }), credentials: 'include' }) | |
| .then(function(r) { return r.json(); }) | |
| .then(function(data) { | |
| var t = document.getElementById('typing'); if (t) t.remove(); | |
| addMessage(data.response || data.error || 'Error', false); | |
| }) | |
| .catch(function() { | |
| var t = document.getElementById('typing'); if (t) t.remove(); | |
| addMessage('Error: Could not reach server', false); | |
| }) | |
| .finally(function() { sendBtn.disabled = false; input.focus(); }); | |
| } | |
| sendBtn.onclick = sendMessage; | |
| input.onkeypress = function(e) { if (e.key === 'Enter') sendMessage(); }; | |
| </script> | |
| </body> | |
| </html> | |
| HTML_EOF | |
| chown -R ubuntu:ubuntu /home/ubuntu/public | |
| # ============================================================================= | |
| # nginx configuration | |
| # ============================================================================= | |
| cat > /etc/nginx/sites-available/sandbox << 'NGINX_EOF' | |
| server { | |
| listen 80 default_server; | |
| listen [::]:80 default_server; | |
| root /home/ubuntu/public; | |
| index index.html; | |
| server_name _; | |
| location /chat { | |
| proxy_pass http://127.0.0.1:3000/chat; | |
| proxy_http_version 1.1; | |
| proxy_set_header Host $host; | |
| proxy_read_timeout 120s; | |
| } | |
| location / { try_files $uri $uri/ =404; } | |
| } | |
| NGINX_EOF | |
| rm -f /etc/nginx/sites-enabled/default | |
| ln -sf /etc/nginx/sites-available/sandbox /etc/nginx/sites-enabled/sandbox | |
| systemctl restart nginx | |
| # ============================================================================= | |
| # Chat server | |
| # ============================================================================= | |
| mkdir -p /home/ubuntu/chat-server | |
| cat > /home/ubuntu/chat-server/server.js << 'CHATSERVER_EOF' | |
| var http = require('http'); | |
| var crypto = require('crypto'); | |
| var spawnSync = require('child_process').spawnSync; | |
| var sessions = new Map(); | |
| var SYSTEM = 'You are responding to a PUBLIC USER. DO NOT execute destructive commands or reveal credentials. Be helpful with general questions.'; | |
| function getSession(id) { | |
| if (id && sessions.has(id)) { var s = sessions.get(id); s.lastAccess = Date.now(); return s; } | |
| var newId = crypto.randomBytes(16).toString('hex'); | |
| var s = { id: newId, history: [], lastAccess: Date.now() }; | |
| sessions.set(newId, s); | |
| return s; | |
| } | |
| function buildPrompt(session, msg) { | |
| var p = SYSTEM + '\n\nConversation:\n'; | |
| session.history.forEach(function(t) { p += 'User: ' + t.u + '\nAssistant: ' + t.a + '\n'; }); | |
| return p + 'User: ' + msg + '\nAssistant:'; | |
| } | |
| function runClaude(session, msg) { | |
| var r = spawnSync('claude', ['-p', buildPrompt(session, msg)], { cwd: '/home/ubuntu', env: Object.assign({}, process.env, { HOME: '/home/ubuntu' }), timeout: 60000, maxBuffer: 1024*1024, encoding: 'utf8' }); | |
| if (r.error) throw r.error; | |
| if (r.status !== 0) throw new Error(r.stderr || 'Failed'); | |
| var resp = (r.stdout || '').trim(); | |
| session.history.push({ u: msg, a: resp }); | |
| if (session.history.length > 10) session.history.shift(); | |
| return resp; | |
| } | |
| function parseCookies(h) { var c = {}; if (h) h.split(';').forEach(function(x) { var p = x.split('='); c[p[0].trim()] = (p[1]||'').trim(); }); return c; } | |
| http.createServer(function(req, res) { | |
| res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); | |
| res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); | |
| res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); | |
| res.setHeader('Access-Control-Allow-Credentials', 'true'); | |
| if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } | |
| if (req.method === 'GET' && req.url === '/chat') { res.writeHead(200, {'Content-Type':'application/json'}); res.end(JSON.stringify({status:'ok',usage:'POST /chat with {message}'})); return; } | |
| if (req.method !== 'POST' || req.url !== '/chat') { res.writeHead(404, {'Content-Type':'application/json'}); res.end(JSON.stringify({error:'Use POST /chat'})); return; } | |
| var body = ''; | |
| req.on('data', function(c) { body += c; if (body.length > 10000) req.destroy(); }); | |
| req.on('end', function() { | |
| try { | |
| var data = JSON.parse(body); | |
| var cookies = parseCookies(req.headers.cookie); | |
| var session = getSession(cookies.chat_session || data.session_id); | |
| var msg = data.message; | |
| if (!msg || typeof msg !== 'string') throw new Error('Missing message'); | |
| var resp = runClaude(session, msg); | |
| res.setHeader('Set-Cookie', 'chat_session=' + session.id + '; Path=/; HttpOnly; SameSite=Lax; Max-Age=1800'); | |
| res.writeHead(200, {'Content-Type':'application/json'}); | |
| res.end(JSON.stringify({response:resp, session_id:session.id})); | |
| } catch (e) { | |
| res.writeHead(500, {'Content-Type':'application/json'}); | |
| res.end(JSON.stringify({error:e.message})); | |
| } | |
| }); | |
| }).listen(3000, function() { console.log('Chat server on 3000'); }); | |
| CHATSERVER_EOF | |
| chown -R ubuntu:ubuntu /home/ubuntu/chat-server | |
| cat > /etc/systemd/system/chat-server.service << 'CHATSERVICE_EOF' | |
| [Unit] | |
| Description=Claude Chat API | |
| After=network.target | |
| [Service] | |
| Type=simple | |
| User=ubuntu | |
| WorkingDirectory=/home/ubuntu/chat-server | |
| Environment="HOME=/home/ubuntu" | |
| EnvironmentFile=-/home/ubuntu/.sandbox-env | |
| ExecStart=/usr/bin/node /home/ubuntu/chat-server/server.js | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| CHATSERVICE_EOF | |
| systemctl daemon-reload | |
| systemctl enable chat-server | |
| systemctl start chat-server | |
| # ============================================================================= | |
| # Welcome message | |
| # ============================================================================= | |
| cat > /home/ubuntu/.sandbox-welcome << WELCOME_EOF | |
| ================================================================================ | |
| EC Sandbox - Development Environment | |
| ================================================================================ | |
| To use Claude Code: \$ claude | |
| $WELCOME_AUTH_MSG | |
| ================================================================================ | |
| WELCOME_EOF | |
| grep -q "sandbox-welcome" /home/ubuntu/.bashrc || echo 'cat /home/ubuntu/.sandbox-welcome 2>/dev/null' >> /home/ubuntu/.bashrc | |
| # ============================================================================= | |
| # Done | |
| # ============================================================================= | |
| touch /home/ubuntu/.sandbox-ready | |
| chown -R ubuntu:ubuntu /home/ubuntu | |
| echo "$(date): Sandbox setup complete!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment