Open-sourced my personal cloud-gaming scripts
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
!jest.config.js
|
||||
*.d.ts
|
||||
*.js
|
||||
node_modules
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
|
||||
bin/*.js
|
||||
lib/*.js
|
||||
@@ -0,0 +1,6 @@
|
||||
*.ts
|
||||
!*.d.ts
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
@@ -0,0 +1,51 @@
|
||||
/* tslint:disable:no-import-side-effect no-submodule-imports no-unused-expression */
|
||||
import "source-map-support/register";
|
||||
import { G4ADStack } from "../lib/g4ad";
|
||||
import {App} from "aws-cdk-lib";
|
||||
import {InstanceSize} from "aws-cdk-lib/aws-ec2";
|
||||
|
||||
const app = new App();
|
||||
|
||||
const NICE_DCV_DISPLAY_DRIVER_URL = "https://d1uj6qtbmh3dt5.cloudfront.net/Drivers/nice-dcv-virtual-display-x64-Release-34.msi";
|
||||
const NICE_DCV_SERVER_URL = "https://d1uj6qtbmh3dt5.cloudfront.net/2021.0/Servers/nice-dcv-server-x64-Release-2021.0-10242.msi";
|
||||
const VOLUME_SIZE_GIB = 150;
|
||||
const OPEN_PORTS = [3389, 8443];
|
||||
const ACCOUNT_ID = "PLACEHOLDER"
|
||||
const REGION = "us-east-1"
|
||||
const VPC_ID = 'PLACEHOLDER'
|
||||
const SUBNET_ID = 'PLACEHOLDER'
|
||||
|
||||
const user = app.node.tryGetContext("user");
|
||||
if (!user) {
|
||||
throw new Error("User is a required parameter. Specify it with `-c user=me`.");
|
||||
}
|
||||
|
||||
const localIp = app.node.tryGetContext("localIp");
|
||||
if (!localIp) {
|
||||
throw new Error("Local IP is a required parameter. Specify it with '-c localIp=XXX.XXX.XXX.XXX'.");
|
||||
}
|
||||
|
||||
const sshKeyName = process.env.AWS_CLOUD_GAMING_SSH_KEY;
|
||||
if (!sshKeyName) {
|
||||
throw new Error("SSH key name is a required parameter. Specify it by setting the environment variable 'AWS_CLOUD_GAMING_SSH_KEY'.");
|
||||
}
|
||||
|
||||
new G4ADStack(app, `TeamBuildingCloudGaming-${user}`, {
|
||||
niceDCVDisplayDriverUrl: NICE_DCV_DISPLAY_DRIVER_URL,
|
||||
niceDCVServerUrl: NICE_DCV_SERVER_URL,
|
||||
instanceSize: InstanceSize.XLARGE4,
|
||||
sshKeyName,
|
||||
volumeSizeGiB: VOLUME_SIZE_GIB,
|
||||
openPorts: OPEN_PORTS,
|
||||
allowInboundCidr: `${localIp}/32`,
|
||||
env: {
|
||||
account: ACCOUNT_ID,
|
||||
region: REGION
|
||||
},
|
||||
tags: {
|
||||
"Application": "cloud-gaming"
|
||||
},
|
||||
user,
|
||||
vpcId: VPC_ID,
|
||||
subnetId: SUBNET_ID
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"app": "npx ts-node bin/cloud-gaming-on-ec2.ts"
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/* tslint:disable:no-submodule-imports quotemark no-unused-expression */
|
||||
|
||||
import {
|
||||
BlockDeviceVolume,
|
||||
CfnLaunchTemplate,
|
||||
EbsDeviceVolumeType,
|
||||
Instance,
|
||||
InstanceSize,
|
||||
MachineImage,
|
||||
Peer,
|
||||
Port,
|
||||
SecurityGroup,
|
||||
Subnet, UserData,
|
||||
Vpc,
|
||||
WindowsVersion,
|
||||
InstanceType
|
||||
} from "aws-cdk-lib/aws-ec2";
|
||||
import {App, CfnOutput, Stack, StackProps} from "aws-cdk-lib";
|
||||
import {ManagedPolicy, Role, ServicePrincipal} from "aws-cdk-lib/aws-iam";
|
||||
|
||||
export interface BaseConfig extends StackProps {
|
||||
readonly instanceSize: InstanceSize;
|
||||
readonly vpcId: string;
|
||||
readonly subnetId: string;
|
||||
readonly sshKeyName: string;
|
||||
readonly volumeSizeGiB: number;
|
||||
readonly niceDCVDisplayDriverUrl: string;
|
||||
readonly niceDCVServerUrl: string;
|
||||
readonly openPorts: number[];
|
||||
readonly allowInboundCidr: string;
|
||||
readonly user: String;
|
||||
}
|
||||
|
||||
export abstract class BaseEc2Stack extends Stack {
|
||||
protected props: BaseConfig;
|
||||
|
||||
constructor(scope: App, id: string, props: BaseConfig) {
|
||||
super(scope, id, props);
|
||||
this.props = props;
|
||||
const { vpcId, subnetId, sshKeyName, volumeSizeGiB, openPorts, allowInboundCidr, user } = props;
|
||||
const vpc = Vpc.fromLookup(this, "Vpc", { vpcId });
|
||||
|
||||
const securityGroup = new SecurityGroup(this, `SecurityGroup-${user}`, {
|
||||
vpc,
|
||||
description: `Allow RDP, and NICE DCV access for ${user}`,
|
||||
securityGroupName: `InboundAccessFromRdpDcvFor${user}`
|
||||
});
|
||||
|
||||
for (const port of openPorts) {
|
||||
securityGroup.connections.allowFrom(Peer.ipv4(allowInboundCidr), Port.tcp(port));
|
||||
}
|
||||
|
||||
const role = new Role(this, `${id}S3Read-${user}`, {
|
||||
roleName: `${id}.GraphicsDriverS3Access-${user}`,
|
||||
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
|
||||
managedPolicies: [
|
||||
ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')
|
||||
],
|
||||
});
|
||||
|
||||
const launchTemplate = new CfnLaunchTemplate(this, `TeamBuildingCloudGamingLaunchTemplate-${user}`, {
|
||||
launchTemplateData: {
|
||||
keyName: sshKeyName,
|
||||
instanceType: this.getInstanceType().toString(),
|
||||
networkInterfaces: [{
|
||||
subnetId,
|
||||
deviceIndex: 0,
|
||||
description: "ENI",
|
||||
groups: [securityGroup.securityGroupId]
|
||||
}],
|
||||
instanceMarketOptions: {
|
||||
spotOptions: {
|
||||
blockDurationMinutes: 120,
|
||||
instanceInterruptionBehavior: "stop",
|
||||
}
|
||||
}
|
||||
},
|
||||
launchTemplateName: `TeamBuildingCloudGamingInstanceLaunchTemplate-${user}/${this.getInstanceType().toString()}`,
|
||||
});
|
||||
|
||||
const ec2Instance = new Instance(this, `EC2Instance-${user}`, {
|
||||
instanceType: this.getInstanceType(),
|
||||
vpc,
|
||||
securityGroup,
|
||||
vpcSubnets: vpc.selectSubnets({ subnets: [Subnet.fromSubnetAttributes(this, 'publicSubnet', {subnetId, availabilityZone: "us-east-1a"})] }),
|
||||
keyName: sshKeyName,
|
||||
userData: this.getUserdata(),
|
||||
machineImage: MachineImage.latestWindows(WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE),
|
||||
blockDevices: [
|
||||
{
|
||||
deviceName: "/dev/sda1",
|
||||
volume: BlockDeviceVolume.ebs(volumeSizeGiB, {
|
||||
volumeType: EbsDeviceVolumeType.GP3,
|
||||
encrypted: true
|
||||
}),
|
||||
}
|
||||
],
|
||||
role,
|
||||
instanceName: `TeamBuildingCloudGaming-${user}/${this.getInstanceType().toString()}`
|
||||
});
|
||||
|
||||
new CfnOutput(this, `Public IP`, { value: ec2Instance.instancePublicIp });
|
||||
|
||||
new CfnOutput(this, `Credentials`, { value: `https://${this.region}.console.aws.amazon.com/ec2/v2/home?region=${this.region}#ConnectToInstance:instanceId=${ec2Instance.instanceId}` });
|
||||
new CfnOutput(this, `InstanceId`, { value: ec2Instance.instanceId });
|
||||
new CfnOutput(this, `KeyName`, { value: sshKeyName });
|
||||
new CfnOutput(this, `LaunchTemplateId`, { value: launchTemplate.launchTemplateName! });
|
||||
}
|
||||
|
||||
protected abstract getUserdata(): UserData;
|
||||
|
||||
protected abstract getInstanceType(): InstanceType;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { BaseConfig, BaseEc2Stack } from "./base";
|
||||
import {App} from "aws-cdk-lib";
|
||||
import {InstanceType, UserData} from "aws-cdk-lib/aws-ec2";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
export interface G4ADConfig extends BaseConfig {
|
||||
|
||||
}
|
||||
|
||||
export class G4ADStack extends BaseEc2Stack {
|
||||
protected props: G4ADConfig;
|
||||
|
||||
constructor(scope: App, id: string, props: G4ADConfig) {
|
||||
super(scope, id, props);
|
||||
}
|
||||
|
||||
protected getUserdata() {
|
||||
const userData = UserData.forWindows();
|
||||
const { niceDCVDisplayDriverUrl, niceDCVServerUrl } = this.props;
|
||||
|
||||
userData.addCommands(
|
||||
`$NiceDCVDisplayDrivers = "${niceDCVDisplayDriverUrl}"`,
|
||||
`$NiceDCVServer = "${niceDCVServerUrl}"`,
|
||||
'$SteamInstallation = "https://cdn.cloudflare.steamstatic.com/client/installer/SteamSetup.exe"',
|
||||
'$MicrosoftEdgeInstallation = "https://go.microsoft.com/fwlink/?linkid=2108834&Channel=Stable&language=en"',
|
||||
`$InstallationFilesFolder = "$home\\Desktop\\InstallationFiles"`,
|
||||
`$Bucket = "ec2-amd-windows-drivers"`,
|
||||
`$KeyPrefix = "latest"`,
|
||||
`$Objects = Get-S3Object -BucketName $Bucket -KeyPrefix $KeyPrefix -Region us-east-1`,
|
||||
`foreach ($Object in $Objects) {
|
||||
$LocalFileName = $Object.Key
|
||||
if ($LocalFileName -ne '' -and $Object.Size -ne 0) {
|
||||
$LocalFilePath = Join-Path $InstallationFilesFolder $LocalFileName
|
||||
Copy-S3Object -BucketName $Bucket -Key $Object.Key -LocalFile $LocalFilePath -Region us-east-1
|
||||
Expand-Archive $LocalFilePath -DestinationPath $InstallationFilesFolder\\1_AMD_driver
|
||||
}
|
||||
}`,
|
||||
'pnputil /add-driver $home\\Desktop\\InstallationFiles\\1_AMD_Driver\\210414a-365562C-Retail_End_User.2\\packages\\Drivers\\Display\\WT6A_INF/*.inf /install',
|
||||
'Invoke-WebRequest -Uri $NiceDCVServer -OutFile $InstallationFilesFolder\\2_NICEDCV-Server.msi',
|
||||
'Invoke-WebRequest -Uri $NiceDCVDisplayDrivers -OutFile $InstallationFilesFolder\\3_NICEDCV-DisplayDriver.msi',
|
||||
'Remove-Item $InstallationFilesFolder\\latest -Recurse',
|
||||
'Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(\'https://community.chocolatey.org/install.ps1\'))',
|
||||
'choco feature enable -n=allowGlobalConfirmation',
|
||||
'choco install steam-rom-manager',
|
||||
'choco install steam-client',
|
||||
'choco install microsoft-edge',
|
||||
`Start-Process msiexec.exe -Wait -ArgumentList '/I C:\\Users\\Administrator\\Desktop\\InstallationFiles\\2_NICEDCV-Server.msi /QN /L* "C:\\msilog.log"'`,
|
||||
`Start-Process msiexec.exe -Wait -ArgumentList '/I C:\\Users\\Administrator\\Desktop\\InstallationFiles\\3_NICEDCV-DisplayDriver.msi /QN /L* "C:\\msilog.log"'`,
|
||||
`'' >> $InstallationFilesFolder\\OK`
|
||||
);
|
||||
|
||||
return userData;
|
||||
}
|
||||
|
||||
protected getInstanceType() {
|
||||
return new InstanceType(`g4ad.${this.props.instanceSize}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "cloud-gaming-on-ec2-instances",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"cdk": "cdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.0",
|
||||
"aws-cdk": "^2.41.0",
|
||||
"ts-node": "^8.1.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-cdk-lib": "^2.41.0",
|
||||
"constructs": "^10.1.96",
|
||||
"source-map-support": "^0.5.16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "commonjs",
|
||||
"lib": ["es2018"],
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"typeRoots": ["./node_modules/@types"]
|
||||
},
|
||||
"exclude": ["cdk.out"]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint:all"
|
||||
],
|
||||
"rules": {
|
||||
"cyclomatic-complexity": false,
|
||||
"increment-decrement": false,
|
||||
"newline-before-return": false,
|
||||
"newline-per-chained-call": false,
|
||||
"no-parameter-properties": false,
|
||||
"no-parameter-reassignment": false,
|
||||
"no-implicit-dependencies": false,
|
||||
"no-unnecessary-class": ["allow-empty-class", "allow-constructor-only"],
|
||||
"file-name-casing": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false,
|
||||
"whitespace": false,
|
||||
"no-unused-variable": false,
|
||||
"no-var-requires": false,
|
||||
"no-console": false,
|
||||
"typedef": false,
|
||||
"unnecessary-else": false,
|
||||
"trailing-comma": false,
|
||||
"comment-format": {
|
||||
"options": [
|
||||
"check-space"
|
||||
]
|
||||
},
|
||||
"member-access": true,
|
||||
"only-arrow-functions": {
|
||||
"options": [
|
||||
"allow-declarations",
|
||||
"allow-named-functions"
|
||||
]
|
||||
},
|
||||
|
||||
"completed-docs": false,
|
||||
"no-any": false,
|
||||
"no-magic-numbers": false,
|
||||
"no-non-null-assertion": false,
|
||||
"no-null-keyword": false,
|
||||
"no-require-imports": false,
|
||||
"no-unbound-method": false,
|
||||
"no-unnecessary-qualifier": false,
|
||||
"no-use-before-declare": false,
|
||||
"no-void-expression": false,
|
||||
"prefer-function-over-method": false,
|
||||
"strict-comparisons": false,
|
||||
"strict-type-predicates": false,
|
||||
"triple-equals": {
|
||||
"options": [
|
||||
"allow-undefined-check"
|
||||
]
|
||||
},
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"member-ordering": {
|
||||
"options": {
|
||||
"order": "statics-first"
|
||||
}
|
||||
},
|
||||
"no-switch-case-fall-through": true,
|
||||
"strict-boolean-expressions": {
|
||||
"options": [
|
||||
"allow-boolean-or-undefined"
|
||||
]
|
||||
},
|
||||
"switch-default": false,
|
||||
"variable-name": {
|
||||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-leading-underscore",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
},
|
||||
"linebreak-style": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user