Skip to content
Open
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
99 changes: 37 additions & 62 deletions src/gateway/r2.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,53 @@
import type { Sandbox } from '@cloudflare/sandbox';
import type { MoltbotEnv } from '../types';
import { R2_MOUNT_PATH, getR2BucketName } from '../config';

const R2_MOUNT_PATH = '/data/moltbot';

/**
* Check if R2 is already mounted by looking at the mount table
* Mount R2 bucket for persistent storage
*/
async function isR2Mounted(sandbox: Sandbox): Promise<boolean> {
try {
const proc = await sandbox.startProcess(`mount | grep "s3fs on ${R2_MOUNT_PATH}"`);
// Wait for the command to complete
let attempts = 0;
while (proc.status === 'running' && attempts < 10) {
// eslint-disable-next-line no-await-in-loop -- intentional sequential polling
await new Promise((r) => setTimeout(r, 200));
attempts++;
export async function mountR2Storage(sandbox: Sandbox, env: MoltbotEnv): Promise<boolean> {
// SIMPLE FIX: Use MOLTBOT_BUCKET binding instead of R2 API credentials
if (env.MOLTBOT_BUCKET && env.R2_BUCKET_NAME) {
try {
console.log('[R2] Mounting R2 storage...');
await sandbox.exec(`mkdir -p ${R2_MOUNT_PATH} && chmod 777 ${R2_MOUNT_PATH}`);
await sandbox.mountBucket(env.MOLTBOT_BUCKET, R2_MOUNT_PATH, {
readOnly: false,
});
console.log('[R2] R2 storage mounted successfully');
return true;
} catch (error) {
console.error('[R2] Mount failed:', error);
return false;
}
const logs = await proc.getLogs();
// If stdout has content, the mount exists
const mounted = !!(logs.stdout && logs.stdout.includes('s3fs'));
console.log('isR2Mounted check:', mounted, 'stdout:', logs.stdout?.slice(0, 100));
return mounted;
} catch (err) {
console.log('isR2Mounted error:', err);
return false;
}

console.log('[R2] R2 not configured (missing MOLTBOT_BUCKET or R2_BUCKET_NAME)');
return false;
}

/**
* Mount R2 bucket for persistent storage
*
* @param sandbox - The sandbox instance
* @param env - Worker environment bindings
* @returns true if mounted successfully, false otherwise
* Sync data to R2
*/
export async function mountR2Storage(sandbox: Sandbox, env: MoltbotEnv): Promise<boolean> {
// Skip if R2 credentials are not configured
if (!env.R2_ACCESS_KEY_ID || !env.R2_SECRET_ACCESS_KEY || !env.CF_ACCOUNT_ID) {
console.log(
'R2 storage not configured (missing R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, or CF_ACCOUNT_ID)',
);
return false;
}

// Check if already mounted first - this avoids errors and is faster
if (await isR2Mounted(sandbox)) {
console.log('R2 bucket already mounted at', R2_MOUNT_PATH);
return true;
}

const bucketName = getR2BucketName(env);
export async function syncToR2(sandbox: Sandbox, env: MoltbotEnv): Promise<{ success: boolean; error?: string }> {
try {
console.log('Mounting R2 bucket', bucketName, 'at', R2_MOUNT_PATH);
await sandbox.mountBucket(bucketName, R2_MOUNT_PATH, {
endpoint: `https://${env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
// Pass credentials explicitly since we use R2_* naming instead of AWS_*
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
});
console.log('R2 bucket mounted successfully - moltbot data will persist across sessions');
return true;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.log('R2 mount error:', errorMessage);

// Check again if it's mounted - the error might be misleading
if (await isR2Mounted(sandbox)) {
console.log('R2 bucket is mounted despite error');
return true;
if (!env.R2_BUCKET_NAME) {
return { success: false, error: 'R2_BUCKET_NAME not set' };
}

// Don't fail if mounting fails - moltbot can still run without persistent storage
console.error('Failed to mount R2 bucket:', err);
return false;
// Create directories
await sandbox.exec(`mkdir -p ${R2_MOUNT_PATH}/openclaw`);

// Sync data
const result = await sandbox.exec(
`rsync -av /root/.openclaw/ ${R2_MOUNT_PATH}/openclaw/ 2>&1`
);

console.log('[R2] Sync completed:', result.stdout);
return { success: true };
} catch (error) {
console.error('[R2] Sync failed:', error);
return { success: false, error: String(error) };
}
}