mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
⭐ feat(www): Finish up on the onboarding (#210)
Merging this prematurely to make sure the team is on the same boat... like dang! We need to find a better way to do this. Plus it has become too big
This commit is contained in:
45
infra/api.ts
45
infra/api.ts
@@ -1,8 +1,8 @@
|
||||
import { vpc } from "./vpc";
|
||||
import { bus } from "./bus";
|
||||
import { domain } from "./dns";
|
||||
import { email } from "./email";
|
||||
import { secret } from "./secret";
|
||||
import { database } from "./database";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
||||
properties: {
|
||||
@@ -14,51 +14,17 @@ export const urls = new sst.Linkable("Urls", {
|
||||
properties: {
|
||||
api: "https://api." + domain,
|
||||
auth: "https://auth." + domain,
|
||||
site: $dev ? "http://localhost:4321" : "https://" + domain,
|
||||
site: $dev ? "http://localhost:3000" : "https://" + domain,
|
||||
},
|
||||
});
|
||||
|
||||
export const authFingerprintKey = new random.RandomString(
|
||||
"AuthFingerprintKey",
|
||||
{
|
||||
length: 32,
|
||||
},
|
||||
);
|
||||
|
||||
export const auth = new sst.aws.Auth("Auth", {
|
||||
issuer: {
|
||||
timeout: "3 minutes",
|
||||
handler: "./packages/functions/src/auth.handler",
|
||||
link: [
|
||||
bus,
|
||||
email,
|
||||
database,
|
||||
authFingerprintKey,
|
||||
secret.PolarSecret,
|
||||
secret.GithubClientID,
|
||||
secret.DiscordClientID,
|
||||
secret.GithubClientSecret,
|
||||
secret.DiscordClientSecret,
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
actions: ["ses:SendEmail"],
|
||||
resources: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: "auth." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
})
|
||||
|
||||
export const apiFunction = new sst.aws.Function("ApiFn", {
|
||||
vpc,
|
||||
handler: "packages/functions/src/api/index.handler",
|
||||
link: [
|
||||
bus,
|
||||
urls,
|
||||
database,
|
||||
postgres,
|
||||
secret.PolarSecret,
|
||||
],
|
||||
timeout: "3 minutes",
|
||||
@@ -77,6 +43,5 @@ export const api = new sst.aws.Router("Api", {
|
||||
})
|
||||
|
||||
export const outputs = {
|
||||
auth: auth.url,
|
||||
api: api.url,
|
||||
};
|
||||
46
infra/auth.ts
Normal file
46
infra/auth.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { vpc } from "./vpc";
|
||||
import { bus } from "./bus";
|
||||
import { domain } from "./dns";
|
||||
import { email } from "./email";
|
||||
import { secret } from "./secret";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const authFingerprintKey = new random.RandomString(
|
||||
"AuthFingerprintKey",
|
||||
{
|
||||
length: 32,
|
||||
},
|
||||
);
|
||||
|
||||
export const auth = new sst.aws.Auth("Auth", {
|
||||
issuer: {
|
||||
vpc,
|
||||
timeout: "3 minutes",
|
||||
handler: "packages/functions/src/auth.handler",
|
||||
link: [
|
||||
bus,
|
||||
email,
|
||||
postgres,
|
||||
authFingerprintKey,
|
||||
secret.PolarSecret,
|
||||
secret.GithubClientID,
|
||||
secret.DiscordClientID,
|
||||
secret.GithubClientSecret,
|
||||
secret.DiscordClientSecret,
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
actions: ["ses:SendEmail"],
|
||||
resources: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: "auth." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
})
|
||||
|
||||
export const outputs = {
|
||||
auth: auth.url,
|
||||
};
|
||||
@@ -1,15 +1,18 @@
|
||||
import { vpc } from "./vpc";
|
||||
import { email } from "./email";
|
||||
import { allSecrets } from "./secret";
|
||||
import { database } from "./database";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const bus = new sst.aws.Bus("Bus");
|
||||
|
||||
bus.subscribe("Event", {
|
||||
vpc,
|
||||
handler: "./packages/functions/src/event/event.handler",
|
||||
link: [
|
||||
database,
|
||||
email,
|
||||
...allSecrets],
|
||||
postgres,
|
||||
...allSecrets
|
||||
],
|
||||
timeout: "5 minutes",
|
||||
permissions: [
|
||||
{
|
||||
|
||||
6
infra/cluster.ts
Normal file
6
infra/cluster.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { vpc } from "./vpc";
|
||||
|
||||
export const cluster = new sst.aws.Cluster("Cluster", {
|
||||
vpc,
|
||||
forceUpgrade: "v2"
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
//Created manually from the dashboard and shared with the whole team/org
|
||||
const dbProject = neon.getProjectOutput({
|
||||
id: "black-sky-26872933"
|
||||
})
|
||||
|
||||
const dbBranchId = $app.stage !== "production" ?
|
||||
new neon.Branch("NeonBranch", {
|
||||
parentId: dbProject.defaultBranchId,
|
||||
projectId: dbProject.id,
|
||||
name: $app.stage,
|
||||
}).id : dbProject.defaultBranchId
|
||||
|
||||
const dbEndpoint = new neon.Endpoint("NeonEndpoint", {
|
||||
projectId: dbProject.id,
|
||||
branchId: dbBranchId,
|
||||
poolerEnabled: true,
|
||||
type: "read_write",
|
||||
})
|
||||
|
||||
const dbRole = new neon.Role("NeonRole", {
|
||||
name: "admin",
|
||||
branchId: dbBranchId,
|
||||
projectId: dbProject.id,
|
||||
})
|
||||
|
||||
const db = new neon.Database("NeonDatabase", {
|
||||
branchId: dbBranchId,
|
||||
projectId: dbProject.id,
|
||||
ownerName: dbRole.name,
|
||||
name: `nestri-${$app.stage}`,
|
||||
})
|
||||
|
||||
export const database = new sst.Linkable("Database", {
|
||||
properties: {
|
||||
name: db.name,
|
||||
user: dbRole.name,
|
||||
host: dbEndpoint.host,
|
||||
password: dbRole.password,
|
||||
},
|
||||
});
|
||||
68
infra/postgres.ts
Normal file
68
infra/postgres.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { vpc } from "./vpc";
|
||||
import { isPermanentStage } from "./stage";
|
||||
|
||||
// TODO: Add a dev db to use, this will help with running zero locally... and testing it
|
||||
export const postgres = new sst.aws.Aurora("Postgres", {
|
||||
vpc,
|
||||
engine: "postgres",
|
||||
scaling: isPermanentStage
|
||||
? undefined
|
||||
: {
|
||||
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 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,
|
||||
});
|
||||
}
|
||||
2
infra/stage.ts
Normal file
2
infra/stage.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const isPermanentStage =
|
||||
$app.stage === "production" || $app.stage === "dev";
|
||||
43
infra/steam.ts
Normal file
43
infra/steam.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { domain } from "./dns";
|
||||
import { cluster } from "./cluster";
|
||||
import { auth } from "./auth";
|
||||
|
||||
export const steam = new sst.aws.Service("Steam", {
|
||||
cluster,
|
||||
wait: true,
|
||||
image: {
|
||||
context: "packages/steam",
|
||||
},
|
||||
loadBalancer: {
|
||||
domain:
|
||||
$app.stage === "production"
|
||||
? undefined
|
||||
: {
|
||||
name: "steam." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
rules: [
|
||||
{ listen: "443/https", forward: "5289/http" },
|
||||
{ listen: "80/http", forward: "5289/http" },
|
||||
],
|
||||
},
|
||||
environment: {
|
||||
NESTRI_AUTH_JWKS_URL: $interpolate`${auth.url}`
|
||||
},
|
||||
scaling:
|
||||
$app.stage === "production"
|
||||
? {
|
||||
min: 2,
|
||||
max: 4,
|
||||
}
|
||||
: undefined,
|
||||
logging: {
|
||||
retention: "1 month",
|
||||
},
|
||||
architecture: "arm64",
|
||||
dev: {
|
||||
directory: "packages/steam",
|
||||
command: "dotnet run",
|
||||
url: "http://localhost:5289",
|
||||
},
|
||||
})
|
||||
1
infra/storage.ts
Normal file
1
infra/storage.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const storage = new sst.aws.Bucket("Storage");
|
||||
17
infra/vpc.ts
Normal file
17
infra/vpc.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// import { isPermanentStage } from "./stage";
|
||||
|
||||
// export const vpc = isPermanentStage
|
||||
// ? new sst.aws.Vpc("Vpc", {
|
||||
// az: 2,
|
||||
// })
|
||||
// //FIXME: Change this ID
|
||||
// : undefined //sst.aws.Vpc.get("Vpc", "vpc-070a1a7598f4c12d1");
|
||||
// //
|
||||
|
||||
export const vpc = new sst.aws.Vpc("NestriVpc", {
|
||||
az: 2,
|
||||
// For lambdas to work in this VPC
|
||||
nat: "ec2",
|
||||
// For SST tunnel to work
|
||||
bastion: true
|
||||
})
|
||||
@@ -1,6 +1,9 @@
|
||||
// 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";
|
||||
import { auth, api } from "./api";
|
||||
import { steam } from "./steam";
|
||||
|
||||
new sst.aws.StaticSite("Web", {
|
||||
path: "./packages/www",
|
||||
@@ -14,7 +17,9 @@ new sst.aws.StaticSite("Web", {
|
||||
},
|
||||
environment: {
|
||||
VITE_API_URL: api.url,
|
||||
VITE_AUTH_URL: auth.url,
|
||||
VITE_STAGE: $app.stage,
|
||||
VITE_AUTH_URL: auth.url,
|
||||
VITE_ZERO_URL: zero.url,
|
||||
VITE_STEAM_URL: steam.url,
|
||||
},
|
||||
})
|
||||
196
infra/zero.ts
Normal file
196
infra/zero.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { vpc } from "./vpc";
|
||||
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.database}`
|
||||
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_SHARD_ID: $app.stage,
|
||||
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
|
||||
...($dev
|
||||
? {
|
||||
}
|
||||
: {
|
||||
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero`,
|
||||
}),
|
||||
};
|
||||
|
||||
// Replication Manager Service
|
||||
const replicationManager = !$dev
|
||||
? new sst.aws.Service(`ZeroReplication`, {
|
||||
cluster,
|
||||
wait: true,
|
||||
...($app.stage === "production"
|
||||
? {
|
||||
cpu: "2 vCPU",
|
||||
memory: "4 GB",
|
||||
}
|
||||
: {}),
|
||||
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",
|
||||
...($app.stage === "production"
|
||||
? {
|
||||
cpu: "2 vCPU",
|
||||
memory: "4 GB",
|
||||
capacity: "spot"
|
||||
}
|
||||
: {
|
||||
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",
|
||||
}),
|
||||
},
|
||||
wait: true,
|
||||
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",
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user