diff --git a/README.md b/README.md index b810bba0..6e88dd08 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,32 @@

+<<<<<<< HEAD

+======= + + + + + Nestri logo + + +

+

Deploy and stream games/apps in the cloud. Use our GPUs or bring your own.

+

+ Discord + Nestri License + Build status + +

+ + + + + +>>>>>>> main diff --git a/infra-old/api.ts b/infra-old/api.ts deleted file mode 100644 index a93e8539..00000000 --- a/infra-old/api.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { bus } from "./bus"; -import { vpc } from "./vpc"; -import { auth } from "./auth"; -import { domain } from "./dns"; -import { secret } from "./secret"; -import { postgres } from "./postgres"; - -const urls = new sst.Linkable("Urls", { - properties: { - api: `https://api.${domain}`, - auth: `https://auth.${domain}`, - site: $dev ? "http://localhost:3000" : `https://console.${domain}`, - } -}) - -const apiFn = new sst.aws.Function("ApiFn", { - vpc, - handler: "packages/functions/src/api/index.handler", - streaming: !$dev, - link: [ - bus, - urls, - auth, - postgres, - secret.SteamApiKey, - secret.PolarSecret, - secret.PolarWebhookSecret, - secret.NestriFamilyMonthly, - secret.NestriFamilyYearly, - secret.NestriFreeMonthly, - secret.NestriProMonthly, - secret.NestriProYearly, - ], - url: true, -}); - -const provider = new aws.Provider("UsEast1", { region: "us-east-1" }); - -const webAcl = new aws.wafv2.WebAcl( - "ApiWaf", - { - scope: "CLOUDFRONT", - defaultAction: { - allow: {}, - }, - visibilityConfig: { - cloudwatchMetricsEnabled: true, - metricName: "api-rate-limit-metric", - sampledRequestsEnabled: true, - }, - rules: [ - { - name: "rate-limit-rule", - priority: 1, - action: { - block: { - customResponse: { - responseCode: 429, - customResponseBodyKey: "rate-limit-response", - }, - }, - }, - statement: { - rateBasedStatement: { - limit: 2 * 60, // 2 rps per authorization header - evaluationWindowSec: 60, - aggregateKeyType: "CUSTOM_KEYS", - customKeys: [ - { - header: { - name: "Authorization", - textTransformations: [{ priority: 0, type: "NONE" }], - }, - }, - ], - }, - }, - visibilityConfig: { - cloudwatchMetricsEnabled: true, - metricName: "rate-limit-rule-metric", - sampledRequestsEnabled: true, - }, - }, - ], - customResponseBodies: [ - { - key: "rate-limit-response", - content: JSON.stringify({ - type: "rate_limit", - code: "too_many_requests", - message: "Rate limit exceeded. Please try again later.", - }), - contentType: "APPLICATION_JSON", - }, - ], - }, - { provider }, -); - -export const api = new sst.aws.Router("Api", { - routes: { - "/*": apiFn.url, - }, - domain: { - name: "api." + domain, - dns: sst.cloudflare.dns(), - }, - transform: { - cdn(args) { - if (!args.transform) { - args.transform = { - distribution: {}, - }; - } - args.transform!.distribution = { - webAclId: webAcl.arn, - }; - }, - }, -}); \ No newline at end of file diff --git a/infra-old/auth.ts b/infra-old/auth.ts deleted file mode 100644 index e10fb4e9..00000000 --- a/infra-old/auth.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { bus } from "./bus"; -import { vpc } from "./vpc"; -import { domain } from "./dns"; -import { secret } from "./secret"; -import { postgres } from "./postgres"; - -export const auth = new sst.aws.Auth("Auth", { - authorizer: { - vpc, - link: [ - bus, - postgres, - secret.PolarSecret, - secret.GithubClientID, - secret.DiscordClientID, - secret.GithubClientSecret, - secret.DiscordClientSecret, - ], - permissions: [ - { - actions: ["ses:SendEmail"], - resources: ["*"], - }, - ], - handler: "packages/functions/src/auth/index.handler", - }, - domain: { - name: "auth." + domain, - dns: sst.cloudflare.dns(), - }, - forceUpgrade: "v2", -}); \ No newline at end of file diff --git a/infra-old/bus.ts b/infra-old/bus.ts deleted file mode 100644 index db624c33..00000000 --- a/infra-old/bus.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { vpc } from "./vpc"; -import { secret } from "./secret"; -import { storage } from "./storage"; -import { postgres } from "./postgres"; - -export const dlq = new sst.aws.Queue("Dlq"); - -export const retryQueue = new sst.aws.Queue("RetryQueue"); - -export const bus = new sst.aws.Bus("Bus"); - -export const eventSub = bus.subscribe("Event", { - vpc, - handler: "packages/functions/src/events/index.handler", - link: [ - // email, - bus, - storage, - postgres, - retryQueue, - secret.PolarSecret, - secret.SteamApiKey - ], - environment: { - RETRIES: "2", - }, - memory: "3002 MB",// For faster processing of large(r) images - timeout: "10 minutes", -}); - -new aws.lambda.FunctionEventInvokeConfig("EventConfig", { - functionName: $resolve([eventSub.nodes.function.name]).apply( - ([name]) => name, - ), - maximumRetryAttempts: 1, - destinationConfig: { - onFailure: { - destination: retryQueue.arn, - }, - }, -}); - -retryQueue.subscribe({ - vpc, - handler: "packages/functions/src/queues/retry.handler", - timeout: "30 seconds", - environment: { - RETRIER_QUEUE_URL: retryQueue.url, - }, - link: [ - dlq, - retryQueue, - eventSub.nodes.function, - ], - permissions: [ - { - actions: ["lambda:GetFunction", "lambda:InvokeFunction"], - resources: [ - $interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:function:*`, - ], - }, - ], - transform: { - function: { - deadLetterConfig: { - targetArn: dlq.arn, - }, - }, - }, -}); \ No newline at end of file diff --git a/infra-old/cluster.ts b/infra-old/cluster.ts deleted file mode 100644 index d2afc758..00000000 --- a/infra-old/cluster.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { vpc } from "./vpc"; - -export const cluster = new sst.aws.Cluster("Cluster", { - vpc, - forceUpgrade: "v2" -}); \ No newline at end of file diff --git a/infra-old/dns.ts b/infra-old/dns.ts deleted file mode 100644 index aa54d355..00000000 --- a/infra-old/dns.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const domain = - { - production: "nestri.io", - dev: "dev.nestri.io", - }[$app.stage] || $app.stage + ".dev.nestri.io"; \ No newline at end of file diff --git a/infra-old/email.ts b/infra-old/email.ts deleted file mode 100644 index 70a3f381..00000000 --- a/infra-old/email.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { domain } from "./dns"; - -export const email = new sst.aws.Email("Email",{ - sender: domain, - dns: sst.cloudflare.dns(), -}) \ No newline at end of file diff --git a/infra-old/postgres.ts b/infra-old/postgres.ts deleted file mode 100644 index 9993f380..00000000 --- a/infra-old/postgres.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { vpc } from "./vpc"; -import { isPermanentStage } from "./stage"; - -export const postgres = !isPermanentStage - ? sst.aws.Aurora.get("Database", "nestri-dev-databasecluster-vmeeabek") - : new sst.aws.Aurora("Database", { - vpc, - engine: "postgres", - scaling: { - min: "0 ACU", - max: "1 ACU", - }, - transform: { - clusterParameterGroup: { - parameters: [ - { - name: "rds.logical_replication", - value: "1", - applyMethod: "pending-reboot", - }, - { - name: "max_slot_wal_keep_size", - value: "10240", - applyMethod: "pending-reboot", - }, - { - name: "rds.force_ssl", - value: "0", - applyMethod: "pending-reboot", - }, - { - name: "max_connections", - value: "1000", - applyMethod: "pending-reboot", - }, - ], - }, - }, - }); - - -new sst.x.DevCommand("Studio", { - link: [postgres], - dev: { - command: "bun db:dev studio", - directory: "packages/core", - autostart: true, - }, -}); - -// const migrator = new sst.aws.Function("DatabaseMigrator", { -// handler: "packages/functions/src/migrator.handler", -// link: [postgres], -// copyFiles: [ -// { -// from: "packages/core/migrations", -// to: "./migrations", -// }, -// ], -// }); - -// if (!$dev) { -// new aws.lambda.Invocation("DatabaseMigratorInvocation", { -// input: Date.now().toString(), -// functionName: migrator.name, -// }); -// } diff --git a/infra-old/realtime.ts b/infra-old/realtime.ts deleted file mode 100644 index 8f7b2bb6..00000000 --- a/infra-old/realtime.ts +++ /dev/null @@ -1,9 +0,0 @@ -// import { auth } from "./auth"; -import { postgres } from "./postgres"; - -export const device = new sst.aws.Realtime("Realtime", { - authorizer: { - link: [ postgres], - handler: "packages/functions/src/realtime/authorizer.handler" - } -}) \ No newline at end of file diff --git a/infra-old/secret.ts b/infra-old/secret.ts deleted file mode 100644 index ddadb712..00000000 --- a/infra-old/secret.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const secret = { - PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY), - SteamApiKey: new sst.Secret("SteamApiKey"), - GithubClientID: new sst.Secret("GithubClientID"), - DiscordClientID: new sst.Secret("DiscordClientID"), - PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"), - GithubClientSecret: new sst.Secret("GithubClientSecret"), - DiscordClientSecret: new sst.Secret("DiscordClientSecret"), - - // Pricing - NestriFreeMonthly: new sst.Secret("NestriFreeMonthly"), - NestriProMonthly: new sst.Secret("NestriProMonthly"), - NestriProYearly: new sst.Secret("NestriProYearly"), - NestriFamilyMonthly: new sst.Secret("NestriFamilyMonthly"), - NestriFamilyYearly: new sst.Secret("NestriFamilyYearly"), -}; - -export const allSecrets = Object.values(secret); \ No newline at end of file diff --git a/infra-old/stage.ts b/infra-old/stage.ts deleted file mode 100644 index 3f4f5335..00000000 --- a/infra-old/stage.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const isPermanentStage = - $app.stage === "production" || $app.stage === "dev"; \ No newline at end of file diff --git a/infra-old/storage.ts b/infra-old/storage.ts deleted file mode 100644 index ef5af0c6..00000000 --- a/infra-old/storage.ts +++ /dev/null @@ -1 +0,0 @@ -export const storage = new sst.aws.Bucket("Storage"); \ No newline at end of file diff --git a/infra-old/vpc.ts b/infra-old/vpc.ts deleted file mode 100644 index 1af7b7b7..00000000 --- a/infra-old/vpc.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { isPermanentStage } from "./stage"; - -export const vpc = isPermanentStage - ? new sst.aws.Vpc("VPC", { - az: 2, - // For lambdas to work in this VPC - nat: "ec2", - // For SST tunnel to work - bastion: true, - }) - : sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748"); \ No newline at end of file diff --git a/infra-old/www.ts b/infra-old/www.ts deleted file mode 100644 index 360c373f..00000000 --- a/infra-old/www.ts +++ /dev/null @@ -1,23 +0,0 @@ -// This is the website part where people play and connect -import { api } from "./api"; -import { auth } from "./auth"; -import { zero } from "./zero"; -import { domain } from "./dns"; - -new sst.aws.StaticSite("Web", { - path: "packages/www", - build: { - output: "./dist", - command: "bun run build", - }, - domain: { - dns: sst.cloudflare.dns(), - name: "console." + domain - }, - environment: { - VITE_API_URL: api.url, - VITE_STAGE: $app.stage, - VITE_AUTH_URL: auth.url, - VITE_ZERO_URL: zero.url, - }, -}) \ No newline at end of file diff --git a/infra-old/zero.ts b/infra-old/zero.ts deleted file mode 100644 index ff8755a8..00000000 --- a/infra-old/zero.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { auth } from "./auth"; -import { domain } from "./dns"; -import { readFileSync } from "fs"; -import { cluster } from "./cluster"; -import { storage } from "./storage"; -import { postgres } from "./postgres"; - -const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`; - -const tag = $dev - ? `latest` - : JSON.parse( - readFileSync("./node_modules/@rocicorp/zero/package.json").toString(), - ).version.replace("+", "-"); - -const zeroEnv = { - FORCE: "1", - NO_COLOR: "1", - ZERO_LOG_LEVEL: "info", - ZERO_LITESTREAM_LOG_LEVEL: "info", - ZERO_UPSTREAM_DB: connectionString, - ZERO_IMAGE_URL: `rocicorp/zero:${tag}`, - ZERO_CVR_DB: connectionString, - ZERO_CHANGE_DB: connectionString, - ZERO_REPLICA_FILE: "/tmp/nestri.db", - ZERO_LITESTREAM_RESTORE_PARALLELISM: "64", - ZERO_APP_ID: $app.stage, - ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`, - ZERO_INITIAL_SYNC_ROW_BATCH_SIZE: "30000", - NODE_OPTIONS: "--max-old-space-size=8192", - ...($dev - ? { - } - : { - ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero/0`, - }), -}; - -// Replication Manager Service -const replicationManager = !$dev - ? new sst.aws.Service(`ZeroReplication`, { - cluster, - wait: true, - cpu: "0.5 vCPU", - memory: "1 GB", - capacity: "spot", - architecture: "arm64", - image: zeroEnv.ZERO_IMAGE_URL, - link: [storage, postgres], - health: { - command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"], - interval: "5 seconds", - retries: 3, - startPeriod: "300 seconds", - }, - environment: { - ...zeroEnv, - ZERO_CHANGE_MAX_CONNS: "3", - ZERO_NUM_SYNC_WORKERS: "0", - }, - logging: { - retention: "1 month", - }, - loadBalancer: { - public: false, - ports: [ - { - listen: "80/http", - forward: "4849/http", - }, - ], - }, - transform: { - loadBalancer: { - idleTimeout: 3600, - }, - service: { - healthCheckGracePeriodSeconds: 900, - }, - }, - }) : undefined; - -// Permissions deployment -// const permissions = new sst.aws.Function( -// "ZeroPermissions", -// { -// vpc, -// link: [postgres], -// handler: "packages/functions/src/zero.handler", -// // environment: { ["ZERO_UPSTREAM_DB"]: connectionString }, -// copyFiles: [{ -// from: "packages/zero/permissions.sql", -// to: "./.permissions.sql" -// }], -// } -// ); - -// if (replicationManager) { -// new aws.lambda.Invocation( -// "ZeroPermissionsInvocation", -// { -// input: Date.now().toString(), -// functionName: permissions.name, -// }, -// { dependsOn: replicationManager } -// ); -// // new command.local.Command( -// // "ZeroPermission", -// // { -// // dir: process.cwd() + "/packages/zero", -// // environment: { -// // ZERO_UPSTREAM_DB: connectionString, -// // }, -// // create: "bun run zero-deploy-permissions", -// // triggers: [Date.now()], -// // }, -// // { -// // dependsOn: [replicationManager], -// // }, -// // ); -// } - -export const zero = new sst.aws.Service("Zero", { - cluster, - image: zeroEnv.ZERO_IMAGE_URL, - link: [storage, postgres], - architecture: "arm64", - cpu: "0.5 vCPU", - memory: "1 GB", - capacity: "spot", - environment: { - ...zeroEnv, - ...($dev - ? { - ZERO_NUM_SYNC_WORKERS: "1", - } - : { - ZERO_CHANGE_STREAMER_URI: replicationManager.url.apply((val) => - val.replace("http://", "ws://"), - ), - ZERO_UPSTREAM_MAX_CONNS: "15", - ZERO_CVR_MAX_CONNS: "160", - }), - }, - health: { - retries: 3, - command: ["CMD-SHELL", "curl -f http://localhost:4848/ || exit 1"], - interval: "5 seconds", - startPeriod: "300 seconds", - }, - loadBalancer: { - domain: { - name: "zero." + domain, - dns: sst.cloudflare.dns() - }, - rules: [ - { listen: "443/https", forward: "4848/http" }, - { listen: "80/http", forward: "4848/http" }, - ], - }, - scaling: { - min: 1, - max: 4, - }, - logging: { - retention: "1 month", - }, - transform: { - service: { - healthCheckGracePeriodSeconds: 900, - }, - // taskDefinition: { - // ephemeralStorage: { - // sizeInGib: 200, - // }, - // }, - loadBalancer: { - idleTimeout: 3600, - }, - }, - dev: { - command: "bun dev", - directory: "packages/zero", - url: "http://localhost:4848", - }, -}); \ No newline at end of file diff --git a/packages/web/.astro/content-assets.mjs b/packages/web/.astro/content-assets.mjs new file mode 100644 index 00000000..2b8b8234 --- /dev/null +++ b/packages/web/.astro/content-assets.mjs @@ -0,0 +1 @@ +export default new Map(); \ No newline at end of file diff --git a/packages/web/.astro/content-modules.mjs b/packages/web/.astro/content-modules.mjs new file mode 100644 index 00000000..2b8b8234 --- /dev/null +++ b/packages/web/.astro/content-modules.mjs @@ -0,0 +1 @@ +export default new Map(); \ No newline at end of file diff --git a/packages/web/.astro/data-store.json b/packages/web/.astro/data-store.json new file mode 100644 index 00000000..c42784d5 --- /dev/null +++ b/packages/web/.astro/data-store.json @@ -0,0 +1 @@ +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.11.2","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false},\"legacy\":{\"collections\":false}}"] \ No newline at end of file diff --git a/packages/web/.astro/settings.json b/packages/web/.astro/settings.json new file mode 100644 index 00000000..8fa98c8f --- /dev/null +++ b/packages/web/.astro/settings.json @@ -0,0 +1,5 @@ +{ + "_variables": { + "lastUpdateCheck": 1752741260450 + } +} \ No newline at end of file diff --git a/packages/web/.astro/types.d.ts b/packages/web/.astro/types.d.ts new file mode 100644 index 00000000..f964fe0c --- /dev/null +++ b/packages/web/.astro/types.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/web/public/logo.black.svg b/packages/web/public/logo.black.svg new file mode 100644 index 00000000..36cf3fb9 --- /dev/null +++ b/packages/web/public/logo.black.svg @@ -0,0 +1,65 @@ + + + + diff --git a/packages/web/public/logo.white.svg b/packages/web/public/logo.white.svg new file mode 100644 index 00000000..3de4a9e6 --- /dev/null +++ b/packages/web/public/logo.white.svg @@ -0,0 +1,65 @@ + + + +