Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions deployments/moltbot-cloud/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Clawdbot VM Image (Built from Source)
# Full Clawdbot gateway running in Hypercore microVMs
# Usage: hypercore spawn clawdbot/clawdbot-vm:latest

FROM node:22-bookworm AS builder

# Install pnpm
RUN npm install -g pnpm

# Copy source code
WORKDIR /build
COPY . .

# Install and build (including UI)
RUN pnpm install --frozen-lockfile
RUN pnpm build
RUN pnpm ui:build

# Create tarball for global install
RUN pnpm pack

# Runtime image - use node base for native module compatibility
FROM node:22-bookworm-slim

# Install base packages and build tools for native modules
RUN apt-get update && apt-get install -y \
curl \
git \
unzip \
ca-certificates \
sudo \
build-essential \
python3 \
&& rm -rf /var/lib/apt/lists/*

# Copy and install built clawdbot
COPY --from=builder /build/*.tgz /tmp/clawdbot.tgz
RUN npm install -g /tmp/clawdbot.tgz && rm /tmp/clawdbot.tgz

# Create clawdbot user
RUN useradd -m -s /bin/bash clawdbot && \
echo "clawdbot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Switch to clawdbot user
USER clawdbot
WORKDIR /home/clawdbot
ENV HOME=/home/clawdbot

# Create config directory
RUN mkdir -p /home/clawdbot/.clawdbot

# Copy entrypoint script
COPY --chown=clawdbot:clawdbot images/clawdbot-vm/entrypoint.sh /opt/entrypoint.sh
Comment thread
mmchougule marked this conversation as resolved.
RUN chmod +x /opt/entrypoint.sh

# Expose gateway port
EXPOSE 18789

# Health check
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost:18789/health || exit 1

# Default environment
ENV CLAWDBOT_PROVIDER=anthropic
ENV CLAWDBOT_MODEL=claude-sonnet-4-20250514
ENV CLAWDBOT_PORT=18789

# Start Clawdbot gateway via entrypoint
CMD ["/opt/entrypoint.sh"]
126 changes: 126 additions & 0 deletions deployments/moltbot-cloud/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/bash
# Clawdbot Cloud Entrypoint
# Fast startup by writing config directly instead of running multiple commands

set -e

echo "Clawdbot Cloud: Starting..."

# Generate a token if not provided (hex only for JSON safety)
GATEWAY_TOKEN="${CLAWDBOT_GATEWAY_TOKEN:-$(openssl rand -hex 16)}"
# Validate token is hex-safe (alphanumeric only, no special chars)
if [[ ! "$GATEWAY_TOKEN" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "WARNING: Token contains special characters, generating safe token"
GATEWAY_TOKEN=$(openssl rand -hex 16)
fi

# Validate port is numeric
GATEWAY_PORT="${CLAWDBOT_PORT:-18789}"
if [[ ! "$GATEWAY_PORT" =~ ^[0-9]+$ ]]; then
echo "WARNING: Invalid port '$GATEWAY_PORT', using default 18789"
GATEWAY_PORT=18789
fi

# Create config directory
mkdir -p ~/.clawdbot

# Write config to moltbot.json (the config file the gateway reads)
cat > ~/.clawdbot/moltbot.json << EOF
{
"gateway": {
"port": ${GATEWAY_PORT},
Comment thread
cursor[bot] marked this conversation as resolved.
"mode": "local",
"bind": "lan",
"auth": {
"mode": "token",
"token": "${GATEWAY_TOKEN}"
Comment thread
cursor[bot] marked this conversation as resolved.
},
"controlUi": {
"dangerouslyDisableDeviceAuth": true
}
},
"auth": {
"profiles": {
"anthropic:default": {
"provider": "anthropic",
"mode": "api_key"
}
}
},
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-sonnet-4-20250514"
},
"workspace": "/home/clawdbot/workspace"
}
}
}
EOF

# Create workspace directory
mkdir -p ~/workspace

echo ""
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ CLAWDBOT CLOUD READY ║"
echo "╚════════════════════════════════════════════════════════════════╝"
echo ""
echo "📋 Token: $GATEWAY_TOKEN"
Comment thread
mmchougule marked this conversation as resolved.
echo ""

# Check API keys and create auth-profiles.json
if [ -n "$ANTHROPIC_API_KEY" ]; then
echo "ANTHROPIC_API_KEY is set (${#ANTHROPIC_API_KEY} chars)"

# Create auth-profiles directory
mkdir -p ~/.clawdbot/agents/main/agent

# Write auth-profiles.json with proper JSON escaping
if command -v jq &> /dev/null; then
# Use jq for safe JSON generation
jq -n \
--arg key "$ANTHROPIC_API_KEY" \
'{
version: 1,
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: $key
}
},
lastGood: {
anthropic: "anthropic:default"
}
}' > ~/.clawdbot/agents/main/agent/auth-profiles.json
else
# Fallback: validate key has no dangerous chars (Anthropic keys are base64-safe)
if [[ "$ANTHROPIC_API_KEY" =~ ^[a-zA-Z0-9_-]+$ ]]; then
cat > ~/.clawdbot/agents/main/agent/auth-profiles.json << AUTHEOF
{
"version": 1,
"profiles": {
"anthropic:default": {
"type": "api_key",
"provider": "anthropic",
"key": "${ANTHROPIC_API_KEY}"
}
},
"lastGood": {
"anthropic": "anthropic:default"
}
}
AUTHEOF
else
echo "ERROR: API key contains invalid characters"
exit 1
fi
fi
echo "Created auth-profiles.json"
else
echo "WARNING: ANTHROPIC_API_KEY is NOT set"
fi

# Start the gateway
exec moltbot gateway run --bind lan --port "${GATEWAY_PORT}"
12 changes: 12 additions & 0 deletions pkg/cluster/serf.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,9 +811,21 @@ func (a *Agent) LogsRequest(id string) (*pb.VmLogsResponse, error) {
//nolint:gocognit
func (a *Agent) monitorWorkloads() {
ticker := time.NewTicker(WorkloadBroadcastPeriod)
gcCounter := 0
for range ticker.C {
ctx := a.ctrRepo.GetContext(context.Background())

// Run CNI garbage collection every 10 iterations (~5 minutes with 30s period)
gcCounter++
if gcCounter >= 10 {
gcCounter = 0
if cleaned, err := a.ctrRepo.GarbageCollectCNI(ctx); err != nil {
a.logger.WithError(err).Warn("CNI garbage collection failed")
} else if cleaned > 0 {
a.logger.Infof("CNI garbage collection cleaned %d orphaned IP allocations", cleaned)
}
}

tasks, err := a.ctrRepo.GetTasks(ctx)
if err != nil {
a.logger.WithError(err).Error("failed to get tasks")
Expand Down
Loading