Last active
May 28, 2025 14:19
-
-
Save bodhihawken/3078b9fb6089965df4171bcd12f48af3 to your computer and use it in GitHub Desktop.
Working SST & Zero Sync Config - Postgres RDS & Local Dev
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
| /* eslint-disable */ | |
| /// <reference path="./.sst/platform/config.d.ts" /> | |
| export default $config({ | |
| app(input) { | |
| return { | |
| name: "booqyy", | |
| removal: input?.stage === "production" ? "retain" : "remove", | |
| home: "aws", | |
| providers: { | |
| command: true, | |
| stripe: "0.0.24", | |
| aws: { | |
| region: "us-west-1", | |
| profile: "booqyy", | |
| } | |
| }, | |
| }; | |
| }, | |
| async run() { | |
| //SECRETS | |
| //// | |
| //// | |
| const execSync = await import("child_process").then((mod) => mod.execSync); | |
| const stripeSecret = new sst.Secret("StripeSecret"); | |
| const stripePublic = new sst.Secret("StripePublic"); | |
| const domain: string = "epicdomainextra.com" | |
| const book_domain: string = "epicdomain.com" | |
| const resendSecret = new sst.Secret("ResendSecret"); | |
| const iglooCompanyKey = new sst.Secret("IglooCompanyKey"); | |
| const zone = "ZONEID" | |
| const provider = new stripe.Provider('Stripe', { | |
| apiKey: stripeSecret.value, | |
| }); | |
| const router = new sst.aws.Router("MyRouter", { | |
| domain: { | |
| name: domain, | |
| dns: sst.aws.dns({ | |
| zone: zone, | |
| }), | |
| } | |
| }); | |
| //// | |
| //SECRETS | |
| //// | |
| sst.Linkable.wrap(stripe.WebhookEndpoint, (endpoint) => { | |
| return { | |
| properties: { | |
| id: endpoint.id, | |
| secret: endpoint.secret, | |
| }, | |
| }; | |
| }); | |
| const webhook = new stripe.WebhookEndpoint("StripeWebhook", { | |
| 'connect': true, | |
| enabledEvents: [ 'checkout.session.completed', 'account.updated', 'identity.verification_session.verified'], | |
| 'url': 'https://'+domain+'/api/stripe/webhook', | |
| }, {provider}); | |
| const webhook_billing = new stripe.WebhookEndpoint("StripeBillingWebhook", { | |
| 'connect': false, | |
| enabledEvents: [ 'checkout.session.completed',], | |
| 'url': 'https://'+domain+'/api/stripe/webhook', | |
| }, {provider}); | |
| const product = new stripe.Product("VolumeSubscription", { | |
| name: "Booqyy Volume Pricing", | |
| description: "Booqyy Subscription", | |
| 'statementDescriptor': 'Booqyy', | |
| 'unitLabel': "Items", | |
| active: true, | |
| metadata: { | |
| }, | |
| }, | |
| {provider}); | |
| const price = new stripe.Price("VolumePricingBQ", { | |
| product: product.id, | |
| currency: "usd", | |
| billingScheme: "tiered", | |
| 'tiersMode': 'volume', | |
| 'tiers': [ | |
| { | |
| 'upTo': 1, | |
| 'unitAmount': 0, | |
| }, | |
| { | |
| 'upTo': 500, | |
| 'unitAmount': 1000, | |
| }, | |
| { | |
| 'upTo': -1, | |
| 'unitAmount': 8000, | |
| }, | |
| ], | |
| recurring: { | |
| interval: "month", | |
| //@ts-ignore | |
| interval_count: 1, | |
| usageType: "licensed", | |
| }, | |
| }, | |
| {provider}); | |
| const zeroVersion = execSync("npm show @rocicorp/zero version") | |
| .toString() | |
| .trim(); | |
| // S3 Bucket | |
| const replicationBucket = new sst.aws.Bucket(`replication-bucket`); | |
| // VPC Configuration | |
| const vpc = new sst.aws.Vpc(`MyNewVpc`, { | |
| az: 2, | |
| 'bastion': true, | |
| nat: "ec2", | |
| }); | |
| const db = new sst.aws.Postgres("Database", { | |
| vpc, | |
| dev: { | |
| database: "booqyy", | |
| username: "postgres", | |
| password: "localbooqyy", | |
| host: "localhost", | |
| port: 54513, | |
| }, | |
| transform: { | |
| parameterGroup: { | |
| parameters: [ | |
| { | |
| name: "rds.logical_replication", | |
| value: "1", | |
| applyMethod: "pending-reboot", | |
| }, | |
| { | |
| name: "rds.force_ssl", | |
| value: "0", | |
| applyMethod: "pending-reboot", | |
| }, | |
| { | |
| name: "max_connections", | |
| value: "1000", | |
| applyMethod: "pending-reboot", | |
| }, | |
| ], | |
| }, | |
| }, | |
| proxy: true | |
| }); | |
| const proxiedDbConnection = $interpolate`postgresql://${db.username}:${db.password}@${db.host}:${db.port}/${db.database}` | |
| const directPostgresConnectionString = $dev | |
| ? $interpolate`postgresql://${db.username}:${db.password}@${db.host}:${db.port}/${db.database}` | |
| : $interpolate`postgresql://${db.username}:${db.password}@${db.nodes.instance?.endpoint}/${db.database}`; | |
| const auth = new sst.aws.Auth("MyAuthHandler", { | |
| issuer: { | |
| vpc, | |
| handler: "auth/index.handler", | |
| link: [resendSecret, stripeSecret, iglooCompanyKey, db], | |
| timeout: "30 seconds", | |
| environment: { | |
| RESEND_API_KEY: resendSecret.value, | |
| ZERO_UPSTREAM_DB: proxiedDbConnection, | |
| }, | |
| }, | |
| domain: { | |
| name: $interpolate`auth.${domain}`, | |
| dns: sst.aws.dns({ | |
| zone: zone, | |
| }), | |
| }, | |
| }); | |
| const UserFrontend = new sst.aws.React("UserFrontend", { | |
| buildCommand: "npm run build", | |
| dev: { | |
| command: "npm run dev_ui", | |
| url: "http://localhost:5173" | |
| }, | |
| router: { | |
| instance: router, | |
| path: "/" | |
| }, | |
| environment: { | |
| VITE_PUBLIC_SERVER: 'https://' + ($app.stage === "production" ? domain : 'localhost:4848'), | |
| VITE_API: 'https://' + domain + '/api', | |
| VITE_AUTH_URL: 'https://auth.' + domain, | |
| VITE_STRIPE_PUBLIC_KEY: stripePublic.value, | |
| VITE_IMAGE_URL: "https://booqyy-images.b-cdn.net" | |
| }, | |
| }); | |
| let imageBucket = new sst.aws.Bucket("ImageBucket", { | |
| access: "public" | |
| }); | |
| const Hono = new sst.aws.Function("Hono", { | |
| vpc, | |
| memory: "1024 MB", | |
| timeout: "5 minutes", | |
| url: { | |
| router: { | |
| instance: router, | |
| path: "/api" | |
| } | |
| }, | |
| handler: "./api/index.handler", | |
| environment: { | |
| STRIPE_SECRET: stripeSecret.value, | |
| FRONTEND: UserFrontend.url, | |
| STRIPE_WEBHOOK_SECRET: webhook.secret, | |
| IMAGE_BUCKET: imageBucket.name, | |
| RESEND_API_KEY: resendSecret.value, | |
| ZERO_UPSTREAM_DB: proxiedDbConnection, | |
| }, | |
| link: [ vpc, auth, stripeSecret, db, webhook, resendSecret, imageBucket, iglooCompanyKey], | |
| }); | |
| // ECS Cluster | |
| const cluster = new sst.aws.Cluster(`cluster`, { | |
| vpc, | |
| }); | |
| //cron job for background message check | |
| const backgroundMessageCheck = new sst.aws.Cron("BackgroundMessageCheck", { | |
| schedule: "rate(15 minutes)", | |
| 'function': { | |
| vpc, | |
| memory: "1024 MB", | |
| timeout: "12 minutes", | |
| handler: "./api/comms.backgroundMessageHandler", | |
| environment: { | |
| STRIPE_SECRET: stripeSecret.value, | |
| FRONTEND: UserFrontend.url, | |
| STRIPE_WEBHOOK_SECRET: webhook.secret, | |
| IMAGE_BUCKET: imageBucket.name, | |
| RESEND_API_KEY: resendSecret.value, | |
| ZERO_UPSTREAM_DB: proxiedDbConnection, | |
| }, | |
| link: [ vpc, auth, stripeSecret, db, webhook, resendSecret, imageBucket, iglooCompanyKey], | |
| }, | |
| }); | |
| // Common environment variables | |
| const commonEnv = { | |
| ZERO_UPSTREAM_DB: directPostgresConnectionString, | |
| ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`, | |
| ZERO_REPLICA_FILE: $app.stage === "production" ? "sync-replica.db" : "./.sst/sync-replica-dev.db", | |
| ZERO_IMAGE_URL: `rocicorp/zero:${zeroVersion}`, | |
| ZERO_CHANGE_MAX_CONNS: "3", | |
| ZERO_CVR_MAX_CONNS: "10", | |
| ZERO_UPSTREAM_MAX_CONNS: "10", | |
| ZERO_PUSH_URL: 'https://' + domain + '/api/push', | |
| }; | |
| if ($app.stage === "production") { | |
| //@ts-ignore | |
| commonEnv.ZERO_LITESTREAM_BACKUP_URL = $interpolate`s3://${replicationBucket.name}/backup`; | |
| } | |
| // Replication Manager Service | |
| const replicationManager = new sst.aws.Service(`replication-manager`, { | |
| dev: { | |
| 'url': 'http://localhost:4849', | |
| }, | |
| cluster, | |
| cpu: "0.5 vCPU", | |
| memory: "1 GB", | |
| architecture: "arm64", | |
| image: commonEnv.ZERO_IMAGE_URL, | |
| link: [replicationBucket, db], | |
| wait: true, | |
| health: { | |
| command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"], | |
| interval: "5 seconds", | |
| retries: 3, | |
| startPeriod: "300 seconds", | |
| }, | |
| environment: { | |
| ...commonEnv, | |
| ZERO_NUM_SYNC_WORKERS: "0", | |
| }, | |
| transform: { | |
| target: { | |
| healthCheck: { | |
| enabled: true, | |
| path: "/keepalive", | |
| protocol: "HTTP", | |
| interval: 5, | |
| healthyThreshold: 2, | |
| timeout: 3, | |
| }, | |
| }, | |
| }, | |
| }); | |
| // View Syncer Service | |
| const viewSyncer = new sst.aws.Service(`view-syncer`, { | |
| dev: { | |
| 'url': 'http://localhost:4848', | |
| 'command': $interpolate`npx zero-cache-dev -p "./shared/schema.ts" `, | |
| }, | |
| cluster, | |
| cpu: "1 vCPU", | |
| memory: "2 GB", | |
| architecture: "arm64", | |
| image: commonEnv.ZERO_IMAGE_URL, | |
| link: [replicationBucket, db], | |
| health: { | |
| command: ["CMD-SHELL", "curl -f http://localhost:4848/ || exit 1"], | |
| interval: "5 seconds", | |
| retries: 3, | |
| startPeriod: "300 seconds", | |
| }, | |
| environment: { | |
| ...commonEnv, | |
| ZERO_CHANGE_STREAMER_MODE: $app.stage === "production" ? "discover" : "dedicated", | |
| }, | |
| logging: { | |
| retention: "1 month", | |
| }, | |
| loadBalancer: { | |
| public: true, | |
| ports: [ | |
| { | |
| listen: "80/http", | |
| forward: "4848/http", | |
| }, | |
| // { | |
| // listen: "443/http", | |
| // forward: "4848/http", | |
| // }, | |
| ], | |
| }, | |
| transform: { | |
| target: { | |
| healthCheck: { | |
| enabled: true, | |
| path: "/keepalive", | |
| protocol: "HTTP", | |
| interval: 5, | |
| healthyThreshold: 2, | |
| timeout: 3, | |
| }, | |
| stickiness: { | |
| enabled: true, | |
| type: "lb_cookie", | |
| cookieDuration: 120, | |
| }, | |
| }, | |
| }, | |
| }); | |
| router.route("/sync", viewSyncer.url) | |
| // Update permissions | |
| new command.local.Command( | |
| "zero-deploy-permissions", | |
| { | |
| create: $app.stage === "production" ? `sst shell npx zero-deploy-permissions -p ../../shared/schema.ts --stage ${$app.stage}` : `echo "Skipping permissions deployment in non-production environment"`, | |
| triggers: [Date.now()], | |
| dir: "./", | |
| environment: { | |
| ZERO_UPSTREAM_DB: $interpolate`${directPostgresConnectionString}`, | |
| ZERO_APP_ID: 'zero' | |
| }, | |
| }, | |
| // after the view-syncer is deployed. | |
| { dependsOn: [viewSyncer, vpc] } | |
| ); | |
| return { | |
| frontend: UserFrontend.url, | |
| zero_url: viewSyncer.url, | |
| image_bucket: imageBucket.domain, | |
| postgres_connection: $interpolate`${directPostgresConnectionString}`, | |
| proxied_postgres_connection: $interpolate`${proxiedDbConnection}`, | |
| } | |
| }, | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment