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
1 change: 0 additions & 1 deletion apps/backup/.gitignore

This file was deleted.

25 changes: 0 additions & 25 deletions apps/backup/Dockerfile

This file was deleted.

24 changes: 0 additions & 24 deletions apps/backup/package.json

This file was deleted.

74 changes: 0 additions & 74 deletions apps/backup/src/backup.ts

This file was deleted.

24 changes: 0 additions & 24 deletions apps/backup/src/env.ts

This file was deleted.

39 changes: 0 additions & 39 deletions apps/backup/src/index.ts

This file was deleted.

11 changes: 0 additions & 11 deletions apps/backup/tsconfig.json

This file was deleted.

33 changes: 33 additions & 0 deletions apps/backups/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# prod
dist/

# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/
.wrangler

# env
.env
.env.production
.dev.vars

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# misc
.DS_Store
21 changes: 21 additions & 0 deletions apps/backups/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "backups",
"scripts": {
"dev": "wrangler dev --test-scheduled",
"deploy": "wrangler deploy --minify",
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.840.0",
"@t3-oss/env-core": "^0.13.8",
"@types/node": "20.14.11",
"db": "workspace:*",
"hono": "^4.8.3",
"typescript": "5.5.3",
"zod": "^3.25.67"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250628.0",
"wrangler": "^4.4.0"
}
}
40 changes: 40 additions & 0 deletions apps/backups/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
BACKUPS_SECRET_KEY: z.string({
description:
"This is a secret key used to sign and verify requests. It should be kept secret and not shared with anyone.",
}),
BACKUPS_DATABSE_NAME: z.string(),
BACKUPS_ORGANIZATION_SLUG: z.string(),
BACKUPS_CLOUDFLARE_ACCOUNT_ID: z.string({
description:
"Account ID for the Cloudflare account. Note that this ID should be the same one the bucket is hosted in.",
}),
BACKUPS_BUCKET_ACCESS_KEY_ID: z.string(),
BACKUPS_BUCKET_SECRET_ACCESS_KEY: z.string(),
BACKUPS_DB_BEARER: z.string({
description:
"This is a bearer token to access the databases. If serverless DB provider allows, try to make this token read-only.",
}),
BACKUPS_BUCKET_NAME: z.string(),
},
onValidationError: (issues) => {
console.log("all process variables:", process.env);
console.error("❌ Invalid environment variables:", issues);
throw new Error("Invalid environment variables");
},
// Called when server variables are accessed on the client.
onInvalidAccess: (variable: string) => {
console.log(
`❌ Attempted to access server-side environment variable "${variable}" on the client.`,
);
throw new Error(
"❌ Attempted to access a server-side environment variable on the client",
);
},
// skipValidation: true,
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});
60 changes: 60 additions & 0 deletions apps/backups/src/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { env } from "./env";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";

const S3 = new S3Client({
region: "auto",
endpoint: `https://${env.BACKUPS_CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: env.BACKUPS_BUCKET_ACCESS_KEY_ID,
secretAccessKey: env.BACKUPS_BUCKET_SECRET_ACCESS_KEY,
},
});

async function getDatabaseDump(databseName: string, organizationSlug: string) {
const res = await fetch(
`https://${databseName}-${organizationSlug}.turso.io/dump`,
{
method: "GET",
headers: new Headers({
Authorization: `Bearer ${env.BACKUPS_DB_BEARER}`,
}),
},
);

if (!res.ok) {
throw new Error(
`Failed to get database dump: ${res.status} ${res.statusText}`,
);
}

return res.text();
}

async function uploadToS3(fileName: string, dumpFile: string) {
const cmd = new PutObjectCommand({
Key: fileName,
Bucket: env.BACKUPS_BUCKET_NAME,
Body: Buffer.from(dumpFile, "utf-8"),
});
return S3.send(cmd);
}

async function doBackup() {
const backupData = await getDatabaseDump(
env.BACKUPS_DATABSE_NAME,
env.BACKUPS_ORGANIZATION_SLUG,
);
const date = new Date().toISOString();
const timestamp = date.replace(/[:.]+/g, "-");
const fileName = `${env.BACKUPS_DATABSE_NAME}-${env.BACKUPS_ORGANIZATION_SLUG}-${timestamp}.sql`;
const res = await uploadToS3(fileName, backupData);

if (res.$metadata.httpStatusCode !== 200) {
throw new Error(
`Failed to upload file to S3: ${res.$metadata.httpStatusCode}`,
);
}
return true;
}

export { doBackup, getDatabaseDump, uploadToS3 };
Loading