-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
84b662c
commit 46cd658
Showing
13 changed files
with
4,677 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.js | ||
!jest.config.js | ||
*.d.ts | ||
node_modules | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,56 @@ | ||
# literal-on-aws | ||
# LiteralAI on AWS | ||
|
||
![AWS](./docs/AWS.png) | ||
|
||
## Prerequisites | ||
|
||
- Node.js 14.15.0 or later | ||
|
||
## Setup | ||
|
||
1. Clone this repository | ||
2. [Create an AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-set-up.html#sign-up-for-aws) | ||
3. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) | ||
4. [Configure AWS CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_auth) `aws configure` | ||
5. `npm install` to install the required dependencies | ||
6. [Bootstrap](https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html) your environment `cdk bootstrap aws://ACCOUNT-NUMBER/REGION` | ||
|
||
- `aws sts get-caller-identity` to get your AWS account number | ||
- `aws configure get region` to get your AWS region | ||
|
||
## Deploy | ||
|
||
7. [Get your Docker Personal Access Token (PAT)](https://docs.getliteral.ai/self-hosting/get-started) | ||
8. Deploy the infra: `npx cdk deploy --all --parameters EcsStack:dockerPat=LITERAL_DOCKER_PAT` | ||
|
||
You should now be able to see the Literal sign in page. | ||
|
||
## Configure HTTPs | ||
|
||
AWS load balancers are not using HTTPs by default. To enable HTTPs (best practice and needed for OAuth): | ||
|
||
1. Create a certificate on AWS (you will have to update your domain DNS) | ||
2. Add an HTTPs listener to the load balancer using that certificate | ||
3. Allow port 443 in the security group | ||
4. Add a CNAME record redirecting your subdomain to the Load Balancer public URL. | ||
|
||
You should now be able to access Literal with HTTPs through your subdomain. | ||
|
||
## Setup OAuth | ||
|
||
Create a new task revision with `NEXTAUTH_URL` set to your HTTPs endpoint (like `https://literalai.mydomain.com`). | ||
|
||
Then add the required [env variables](https://docs.getliteral.ai/self-hosting/deployment#provider-specific) specific to your OAuth provider. | ||
|
||
Do not forget to allow the OAuth redirect url on your OAuth provider (like `https://literalai.mydomain.com/api/auth/callback/google`) | ||
|
||
Finally, update your container with the latest revision. | ||
|
||
## AWS CDK Useful commands | ||
|
||
- `npm run build` compile typescript to js | ||
- `npm run watch` watch for changes and compile | ||
- `npm run test` perform the jest unit tests | ||
- `npx cdk deploy` deploy this stack to your default AWS account/region | ||
- `npx cdk diff` compare deployed stack with current state | ||
- `npx cdk synth` emits the synthesized CloudFormation template |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/usr/bin/env node | ||
import 'source-map-support/register'; | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { NetworkStack } from '../lib/network-stack'; | ||
import { EcsStack } from '../lib/ecs-stack'; | ||
import { DataStack } from '../lib/data-stack'; | ||
|
||
const app = new cdk.App(); | ||
|
||
const networkStack = new NetworkStack(app, 'NetworkStack', {}); | ||
|
||
const dataStack = new DataStack(app, 'DataStack', { | ||
vpc: networkStack.vpc, | ||
dataSecurityGroup: networkStack.dataSecurityGroup, | ||
elasticacheSubnetGroup: networkStack.elasticacheSubnetGroup, | ||
}); | ||
|
||
new EcsStack(app, 'EcsStack', { | ||
vpc: networkStack.vpc, | ||
ecsSecurityGroup: networkStack.ecsSecurityGroup, | ||
alb: networkStack.alb, | ||
bucket: dataStack.bucket, | ||
cache: dataStack.cache, | ||
db: dataStack.db, | ||
listener: networkStack.listener, | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
{ | ||
"app": "npx ts-node --prefer-ts-exts bin/literal.ts", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"**/*.d.ts", | ||
"**/*.js", | ||
"tsconfig.json", | ||
"package*.json", | ||
"yarn.lock", | ||
"node_modules", | ||
"test" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": [ | ||
"aws", | ||
"aws-cn" | ||
], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-efs:denyAnonymousAccess": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, | ||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, | ||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, | ||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, | ||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, | ||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, | ||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, | ||
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, | ||
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, | ||
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, | ||
"@aws-cdk/aws-eks:nodegroupNameAttribute": true, | ||
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
testEnvironment: 'node', | ||
roots: ['<rootDir>/test'], | ||
testMatch: ['**/*.test.ts'], | ||
transform: { | ||
'^.+\\.tsx?$': 'ts-jest' | ||
} | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { Construct } from 'constructs'; | ||
import * as ec2 from 'aws-cdk-lib/aws-ec2'; | ||
import * as rds from 'aws-cdk-lib/aws-rds'; | ||
import * as elasticache from 'aws-cdk-lib/aws-elasticache'; | ||
import * as s3 from 'aws-cdk-lib/aws-s3'; | ||
|
||
interface DataStackProps extends cdk.StackProps { | ||
vpc: ec2.Vpc; | ||
dataSecurityGroup: ec2.SecurityGroup; | ||
elasticacheSubnetGroup: elasticache.CfnSubnetGroup; | ||
} | ||
|
||
export class DataStack extends cdk.Stack { | ||
public readonly bucket: s3.Bucket; | ||
public readonly cache: elasticache.CfnCacheCluster; | ||
public readonly db: rds.DatabaseInstance; | ||
|
||
constructor(scope: Construct, id: string, props: DataStackProps) { | ||
super(scope, id, props); | ||
|
||
const db = new rds.DatabaseInstance(this, 'Database', { | ||
engine: rds.DatabaseInstanceEngine.postgres({ | ||
version: rds.PostgresEngineVersion.VER_16_2 | ||
}), | ||
instanceType: new ec2.InstanceType('t3.micro'), | ||
vpc: props.vpc, | ||
vpcSubnets: props.vpc.selectSubnets({subnetGroupName: 'data'}), | ||
databaseName: 'platform', | ||
credentials: rds.Credentials.fromGeneratedSecret('literalai'), | ||
deletionProtection: true, | ||
allocatedStorage: 100, | ||
securityGroups: [props.dataSecurityGroup] | ||
}); | ||
|
||
// Elasticache | ||
const cache = new elasticache.CfnCacheCluster(this, 'Cache', { | ||
cacheNodeType: 'cache.t3.micro', | ||
engine: 'redis', | ||
numCacheNodes: 1, | ||
cacheSubnetGroupName: props.elasticacheSubnetGroup.ref, | ||
vpcSecurityGroupIds: [props.dataSecurityGroup.securityGroupId] | ||
}); | ||
|
||
// S3 | ||
const bucket = new s3.Bucket(this, 'Bucket', { | ||
removalPolicy: cdk.RemovalPolicy.DESTROY, | ||
cors: [ | ||
{ | ||
allowedOrigins: ['*'], | ||
allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.POST], | ||
allowedHeaders: ['*'], | ||
} | ||
], | ||
}); | ||
|
||
this.bucket = bucket; | ||
this.cache = cache; | ||
this.db = db; | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { Construct } from 'constructs'; | ||
import * as ec2 from 'aws-cdk-lib/aws-ec2'; | ||
import * as ecs from 'aws-cdk-lib/aws-ecs'; | ||
import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationListener } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; | ||
import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; | ||
import * as rds from 'aws-cdk-lib/aws-rds'; | ||
import * as elasticache from 'aws-cdk-lib/aws-elasticache'; | ||
import * as s3 from 'aws-cdk-lib/aws-s3'; | ||
import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; | ||
|
||
interface EcsStackProps extends cdk.StackProps { | ||
vpc: ec2.Vpc; | ||
ecsSecurityGroup: ec2.SecurityGroup; | ||
alb: ApplicationLoadBalancer; | ||
bucket: s3.Bucket; | ||
cache: elasticache.CfnCacheCluster; | ||
db: rds.DatabaseInstance; | ||
listener: ApplicationListener; | ||
} | ||
|
||
export class EcsStack extends cdk.Stack { | ||
constructor(scope: Construct, id: string, props: EcsStackProps) { | ||
super(scope, id, props); | ||
|
||
const literalDockerPat = new cdk.CfnParameter(this, 'dockerPat', { | ||
type: 'String', | ||
description: 'Dockerhub token for Literalai', | ||
}).valueAsString; | ||
|
||
// Secrets | ||
const secret = new Secret(this, 'LiteralaiDockerhub', { | ||
secretStringValue: cdk.SecretValue.unsafePlainText(JSON.stringify({ | ||
username: 'literalai', | ||
password: literalDockerPat | ||
})) | ||
}); | ||
|
||
const nextAuthSecret = new Secret(this, 'NextAuthSecret', { | ||
generateSecretString: { | ||
excludePunctuation: true, | ||
}, | ||
}); | ||
|
||
const cluster = new ecs.Cluster(this, 'Cluster', { vpc: props.vpc }); | ||
|
||
const task = new ecs.FargateTaskDefinition(this, 'task', { | ||
memoryLimitMiB: 2048, | ||
cpu: 1024, | ||
}); | ||
|
||
task.addToTaskRolePolicy(new PolicyStatement({ | ||
actions: ['s3:*'], | ||
resources: [props.bucket.bucketArn] | ||
})); | ||
|
||
task.addToExecutionRolePolicy(new PolicyStatement({ | ||
actions: ['s3:*'], | ||
resources: ['*'] | ||
})); | ||
|
||
const service = new ecs.FargateService(this, 'Service', { | ||
cluster: cluster, | ||
assignPublicIp: true, | ||
vpcSubnets: props.vpc.selectSubnets({subnetGroupName: 'application'}), | ||
taskDefinition: task, | ||
desiredCount: 1, | ||
capacityProviderStrategies: [{ | ||
capacityProvider: 'FARGATE', | ||
weight: 1 | ||
}], | ||
enableExecuteCommand: true, | ||
securityGroups: [props.ecsSecurityGroup], | ||
circuitBreaker: { | ||
rollback: true | ||
} | ||
}); | ||
|
||
const logging = new ecs.AwsLogDriver({ streamPrefix: "service" }) | ||
|
||
const dbSecret = Secret.fromSecretNameV2(this, 'DBSecret', props.db.secret?.secretName || ''); | ||
|
||
const backend = task.addContainer('backend', { | ||
image: ecs.ContainerImage.fromRegistry('docker.io/literalai/platform:latest', { | ||
credentials: secret | ||
}), | ||
portMappings: [{ | ||
containerPort: 3000 | ||
}], | ||
logging, | ||
environment: { | ||
REDIS_URL: `redis://${props.cache.attrRedisEndpointAddress}:${props.cache.attrRedisEndpointPort}`, | ||
DATABASE_SSL: 'true', | ||
BUCKET_NAME: props.bucket.bucketName, | ||
NEXTAUTH_URL: `http://${props.alb.loadBalancerDnsName}`, | ||
}, | ||
environmentFiles: [ | ||
ecs.EnvironmentFile.fromAsset('./literal.container.env') | ||
], | ||
secrets: { | ||
DATABASE_HOST: ecs.Secret.fromSecretsManager(dbSecret, 'host'), | ||
DATABASE_PORT: ecs.Secret.fromSecretsManager(dbSecret, 'port'), | ||
DATABASE_NAME: ecs.Secret.fromSecretsManager(dbSecret, 'dbname'), | ||
DATABASE_USERNAME: ecs.Secret.fromSecretsManager(dbSecret, 'username'), | ||
DATABASE_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), | ||
NEXTAUTH_SECRET: ecs.Secret.fromSecretsManager(nextAuthSecret), | ||
} | ||
}); | ||
|
||
props.listener.addTargets('Service', { | ||
port: 3000, | ||
protocol: ApplicationProtocol.HTTP, | ||
targets: [service], | ||
healthCheck: { | ||
path: '/', | ||
interval: cdk.Duration.seconds(10), | ||
timeout: cdk.Duration.seconds(5), | ||
} | ||
}); | ||
|
||
new cdk.CfnOutput(this, 'AlbDnsName', { value: `http://${props.alb.loadBalancerDnsName}` }); | ||
} | ||
} |
Oops, something went wrong.