Open-sourced my personal cloud-gaming scripts

This commit is contained in:
hamilcarBarca17
2023-02-17 13:34:55 -07:00
parent caab29d3c4
commit 87538d511f
17 changed files with 1351 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
.classpath
.project
.settings
.eclipse/*
.gradle
*.launch
*maven-eclipse.xml
\#*
.\#*
*~
*.swp
.idea
*.iml
*.ipr
*.iws
*.ids
target
build
out
*.class
junit*.properties
*.orig
nb-*
.DS_Store
.metadata
classes
*.MF
*.retry
+99
View File
@@ -0,0 +1,99 @@
# AWS Cloud Gaming
This repo will automate the creation and connection to an AWS EC2 spot instance to be used for cloud gaming.
## Prerequisites
* Running on an Ubuntu system
* [AWS CLI (v2)](https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip) is installed
* [Node.js](https://linuxize.com/post/how-to-install-node-js-on-ubuntu-22-04/) and NPM are installed
* You have sudo permissions on your current system
## Configuration
### Environment Variables
| Name | Description | Example |
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|
| `AWS_CLOUD_GAMING_PROFILE` | The AWS profile to use corresponding to a profile in your AWS config (usually ~/.aws/config).<br/><br/> Defaults to `personal`<br/><br/> This profile should have permissions to create the appropriate resources in AWS, including CloudFormation stacks, and EC2 instances | `AWS_CLOUD_GAMING_PROFILE=uber-sandbox` |
| `AWS_CLOUD_GAMING_SSH_KEY` | The name of some key pair that exists in AWS and that you have locally in your `~/.ssh` directory | `AWS_CLOUD_GAMING_SSH_KEY=team-building` |
### CDK Variables
Modify the following properties in the [cloud-gaming-on-ec2](cdk/bin/cloud-gaming-on-ec2.ts) stack:
| Parameter Name | Description |
|----------------|---------------------------------------------------------------------|
| `ACCOUNT_ID` | The AWS account ID you want to use |
| `REGION` | The AWS region in which you want the resources created |
| `VPC_ID` | The ID of the VPC you wish to deploy the instance into |
| `SUBNET_ID` | The ID of a public subnet that you want your instance deployed into |
## Running the application
To run the application, simply run the `cloud-gaming.sh` script in the root directory and follow all instructions/menu choices and the script will take care of everything else!
## Debugging
The scripts output logs to `/tmp/cloud-gaming.log` for easy debugging and auditing.
Note that CDK specific logs are output for each CDK task (`synth`, `bootstrap`, `deploy`) in their own log files to make debugging the CDK easier:
* `synth` outputs logs to `/tmp/cdk-synth.log`
* `bootstrap` outputs logs to `/tmp/cdk-bootstrap.log`
* `deploy` outputs logs to `/tmp/cdk-deploy.log`
## Customizing the EC2 Instance
### Change the Instance type
To change the instance type, simply create a new stack that subclasses the [base.ts](cdk/lib/base.ts), and override the `getUserData()` and `getInstanceType()`
methods to change the type, and customize the user data for the instance. Just make sure to add the `new` call to this stack in [cloud-gaming-on-ec2.ts](cdk/bin/cloud-gaming-on-ec2.ts)
### Log into instance desktop
You can log into the desktop of the instance via the `Manage Personal Instance` menu in the `cloud-gaming.sh` script.
### Managing instance state
All personal instance management can be achieved via the `Manage Personal Instance` menu in the `cloud-gaming.sh` script.
This includes
* Checking the state of the instance (`started`, `stopped`, `terminated`, etc.)
* Start the instance
* Stop the instance
## Managing the Stream
There are two types of streams this project enables: Personal and Shared
### Personal Streams
A personal stream is a Steam Link stream to your personal EC2 instance. This is ideal for online multiplayer games where players all play on their own machines.
You can start a stream to your personal instance via the `Stream Settings` menu in the `cloud-gaming.sh` script. This will prompt to start your personal instance if it's not already started.
### Shared Streams
A shared stream is a Steam Link stream to an instance that multiple people are connecting to at once. This is ideal for couch co-op games like Super Smash Bros, Mario Kart, etc.
where everyone needs to be on the same machine.
You can either be a host or a player in a shared stream.
Hosts host the shared stream on their EC2 instance, and players are the other players connecting to that same instance.
The `Stream Settings` menu in the `cloud-gaming.sh` script will guide you through the setup of either the host or player stream for a shared stream.
Note: A Shared stream will not overwrite your settings to connect to your personal instance. You'll just have to change back to your personal instance in Steam Link via the Gear icon -> Computers menu.
## Built With
* [Bash](https://www.gnu.org/software/bash/) - Shell that all the scripts are written in
* [Node.js](https://nodejs.org/en/) - JS runtime for CDK
* [TypeScript](https://www.typescriptlang.org/) - CDK stacks and constructs are written in TS
* [AWS CDK](https://aws.amazon.com/cdk/) - AWS Cloud Development Kit is IaC using familiar programming languages. It's used to define the EC2 instance
* [AWS CLI](https://aws.amazon.com/cli/) - Used to manage the EC2 instance; e.g. get credentials for instance, start/stop/restart instance, check instance status, etc.
* [Whiptail](https://linux.die.net/man/1/whiptail) - Used to create a TUI for the user
* [Dialog](https://linux.die.net/man/1/dialog) - Used to display tail boxes for long-running processes like CDK deploys
* [NICE DCV](https://aws.amazon.com/hpc/dcv/) - High-performance RDP for connecting to EC2 instance desktop
* [Xvfb](https://www.x.org/archive/X11R7.6/doc/man/man1/Xvfb.1.xhtml) - X Virtual FrameBuffer, used to open a connection to your instance via NICE DCV in the background. Necessary to allow SteamLink connections to your instance
* [Steam Link](https://store.steampowered.com/app/353380/Steam_Link/) - High quality, low latency stream from your machine to your EC2 instance that forwards all inputs, controllers or otherwise.
* [Flatpak](https://flatpak.org/) - Used to install Steam Link (Ubuntu only)
* [xdotool](https://manpages.ubuntu.com/manpages/trusty/man1/xdotool.1.html) - X11 automation tool to minimize the terminal
* [pulsemixer](https://github.com/GeorgeFilipkin/pulsemixer) - Used to mute NICE DCV running in Xvfb so there's no echoing of sound between Steam Link and NICE DCV
* [nc](http://netcat.sourceforge.net/) - Ping the EC2 instance on port 8443 to see when NICE DCV is running
+230
View File
@@ -0,0 +1,230 @@
#!/bin/bash
source ~/.bashrc
source logger.sh
source utils.sh
if [[ -z $AWS_CLOUD_GAMING_PROFILE ]]; then
AWS_CLOUD_GAMING_PROFILE=personal
fi
checkAwsProfile() {
printInfo "Checking for the $AWS_CLOUD_GAMING_PROFILE AWS Profile in AWS config"
if ! (aws --profile $AWS_CLOUD_GAMING_PROFILE sts get-caller-identity --no-cli-auto-prompt > /dev/null 2>&1); then
printError "AWS config is missing the $AWS_CLOUD_GAMING_PROFILE profile. Add it to your ~/.aws/config file and try running this application again"
exit 1
fi
}
getInstanceState() {
aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 describe-instances --instance-ids "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --no-cli-auto-prompt --output text &
}
showGaugeBoxForAwsCommand() {
declare increment
declare i
increment=$(echo "scale=1; 100 / 30" | bc)
i="0"
printInfo "$2"
{
while [[ $(getInstanceState) != "$1" ]]; do
declare percent
percent=$(printf "%.0f" $i)
echo -e "XXX\n$percent\n$2... \nXXX"
i=$(echo "$i + $increment" | bc)
sleep 5
done
if [[ $(getInstanceState) != "$1" ]]; then
printError "$4"
echo -e "XXX\n0\n$4\nXXX"
return 1
else
echo -e "XXX\n100\n$3\nXXX"
sleep 1
fi
} | whiptail --title "$2..." --gauge "$2..." "$GAUGE_BOX_HEIGHT" "$GAUGE_BOX_WIDTH" 0
printInfo "$3"
}
waitForInstanceToBecomeAvailable() {
printInfo "Waiting for instance to become available"
{
declare increment
increment=$(echo "scale=1; 100/90" | bc)
for ((i=0; i<=100; i=$(printf "%.0f" $(echo "scale=1; $i + $increment" | bc)))); do
if (timeout 10s nc -vz "$AWS_TEAM_BUILDING_EC2_INSTANCE_IP" 8443); then
break
fi
echo "$i"
done
echo 100
sleep 1
} | whiptail --title "Instance Startup" --gauge "Waiting for instance to become available..." "$GAUGE_BOX_HEIGHT" "$GAUGE_BOX_WIDTH" 0
}
getInstanceIp() {
printInfo "Fetching instance IP for id: $AWS_TEAM_BUILDING_EC2_INSTANCE_ID"
aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 describe-instances --instance-ids "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --query "Reservations[0].Instances[0].PublicIpAddress" --output text --no-cli-auto-prompt
}
createDcvConnectionProfileFromTemplate() {
printInfo "Creating DCV connection profile from template"
PASSWORD="$(aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 get-password-data --instance-id "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --priv-launch-key ~/.ssh/insights-team-building-key-pair.pem --query 'PasswordData' --output text --no-cli-auto-prompt)"
PASSWORD=$(echo -n $PASSWORD)
sed -i "/^host=/c\host=$AWS_TEAM_BUILDING_EC2_INSTANCE_IP" cloud_gaming_dcv_profile.dcv
sed -i "/^password=/c\password=$PASSWORD" cloud_gaming_dcv_profile.dcv
}
startInstance() {
declare state
declare desiredState=running
state=$(getInstanceState)
printInfo "Starting instance"
if [[ $state == "$desiredState" ]]; then
printError "Instance is already running. Doing nothing"
msgBox "Instance is already running."
else
declare instanceIp
aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 start-instances --instance-ids "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --no-cli-auto-prompt > /dev/null 2>&1 &
showGaugeBoxForAwsCommand "$desiredState" "Starting Your Instance" "Successfully Started Your Instance!" "Failed to start your instance!"
printInfo "Checking to see if IP changed"
instanceIp=$(getInstanceIp)
if [[ $instanceIp != $AWS_TEAM_BUILDING_EC2_INSTANCE_IP ]]; then
setConfigValue "AWS_TEAM_BUILDING_EC2_INSTANCE_IP" "$instanceIp"
createDcvConnectionProfileFromTemplate
fi
waitForInstanceToBecomeAvailable
fi
}
stopInstance() {
declare state
declare desiredState=stopped
state=$(getInstanceState)
printInfo "Stopping instance"
if [[ $state == "$desiredState" ]]; then
printError "Instance is already stopped."
msgBox "Instance is already stopped."
else
aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 stop-instances --instance-ids "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --no-cli-auto-prompt > /dev/null 2>&1 &
showGaugeBoxForAwsCommand "$desiredState" "Stopping Your Instance" "Successfully Stopped Your Instance!" "Failed to stop your instance!"
fi
}
rebootInstance() {
declare desiredState=running
printInfo "Rebooting instance"
aws --profile $AWS_CLOUD_GAMING_PROFILE ec2 reboot-instances --instance-ids "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID" --no-cli-auto-prompt > /dev/null 2>&1 &
if ! (showGaugeBoxForAwsCommand "$desiredState" "Restarting Your Instance" "Successfully Restarted Your Instance!"); then
printError "Failed to restart instance. Waiting for user to manually start instance before continuing."
msgBox "$(cat <<-EOF
Failed to restart your instance! Please make sure your instance is started before continuing!
Your instance ID is $AWS_TEAM_BUILDING_EC2_INSTANCE_ID
Hit 'OK' Once your instance is started
EOF
)"
fi
}
getMyIp() {
curl -s -L -X GET http://checkip.amazonaws.com
}
deployCdk() {
printInfo "Deploying CDK"
cd cdk
declare user
declare localIp
declare logFile=/tmp/cdk
user="$(whoami)"
localIp="$(getMyIp)"
{
echo -e "XXX\n0\nRunning npm install... \nXXX"
printInfo "Running npm install"
npm install > /dev/null 2>&1
echo -e "XXX\n50\nBuilding CDK... \nXXX"
printInfo "Running npm run build"
npm run build > /dev/null 2>&1
echo -e "XXX\n100\nDone! \nXXX"
sleep 1
} | whiptail --title "Preparing CDK..." --gauge "Preparing CDK..." "$GAUGE_BOX_HEIGHT" "$GAUGE_BOX_WIDTH" 0
declare pid
declare synthLogFile="${logFile}-synth.log"
printInfo "Running CDK synth and logging to $synthLogFile"
yes | npx cdk --no-color --require-approval never --profile $AWS_CLOUD_GAMING_PROFILE -c "user=$user" -c "localIp=$localIp" synth "TeamBuildingCloudGaming-$user" > $synthLogFile 2>&1 &
pid=$!
showTailBox "Synthesizing CDK" $pid $synthLogFile
declare bootstrapLogFile="${logFile}-bootstrap.log"
printInfo "Bootstrapping CDK and logging to $bootstrapLogFile"
yes | npx cdk --no-color --require-approval never --profile $AWS_CLOUD_GAMING_PROFILE -c "user=$user" -c "localIp=$localIp" bootstrap > $bootstrapLogFile 2>&1 &
pid=$!
showTailBox "Bootstrapping CDK" $pid $bootstrapLogFile
declare deployLogFile="${logFile}-deploy.log"
printInfo "Deploying CDK and logging to $deployLogFile"
yes | npx cdk --no-color --require-approval never --profile $AWS_CLOUD_GAMING_PROFILE -c "user=$user" -c "localIp=$localIp" deploy "TeamBuildingCloudGaming-$user" > $deployLogFile 2>&1 &
pid=$!
showTailBox "Deploy EC2 Instance" $pid $deployLogFile
unset AWS_TEAM_BUILDING_EC2_INSTANCE_ID
unset AWS_TEAM_BUILDING_EC2_INSTANCE_IP
AWS_TEAM_BUILDING_EC2_INSTANCE_ID=$(cat $deployLogFile | grep InstanceId | awk '{print $NF;}')
setConfigValue "AWS_TEAM_BUILDING_EC2_INSTANCE_ID" "$AWS_TEAM_BUILDING_EC2_INSTANCE_ID"
AWS_TEAM_BUILDING_EC2_INSTANCE_IP=$(getInstanceIp)
setConfigValue "AWS_TEAM_BUILDING_EC2_INSTANCE_IP" "$AWS_TEAM_BUILDING_EC2_INSTANCE_IP"
cd ..
waitForInstanceToBecomeAvailable
rebootInstance
waitForInstanceToBecomeAvailable
}
connectToInstanceViaDcvViewer() {
printInfo "Connecting to instance desktop via DCV Viewer"
if ! (hash dcvviewer 2> /dev/null); then
printError "dcvviewer is not installed. Cannot connect to personal instance until first time setup is run."
msgBox "Can't connect to personal instance via DCV Viewer without first time setup. Run the deploy instance task from the instance management menu first!"
fi
if (pgrep -f dcvviewer); then
printError "DCV Viewer is already running."
msgBox "DCV Viewer is already running"
return 0
fi
declare state
state=$(getInstanceState)
if [[ $state != "running" ]]; then
if (whiptail --fb --title "Start your instance?" --yesno "In order to stream, you instance needs to be started. Would you like to start your personal instance?" "$BOX_BOX_HEIGHT" "$BOX_WIDTH"); then
startInstance
else
printError "Unable to start desktop connection: Instance is not running"
msgBox "Can't start desktop connection! Instance is not running. You can start the instance from the personal Instance Management menu."
return 0
fi
fi
dcvviewer cloud_gaming_dcv_profile.dcv --certificate-validation-policy=accept-untrusted > /dev/null 2>&1 &
}
+11
View File
@@ -0,0 +1,11 @@
!jest.config.js
*.d.ts
*.js
node_modules
# CDK asset staging directory
.cdk.staging
cdk.out
bin/*.js
lib/*.js
+6
View File
@@ -0,0 +1,6 @@
*.ts
!*.d.ts
# CDK asset staging directory
.cdk.staging
cdk.out
+51
View File
@@ -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
});
+3
View File
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/cloud-gaming-on-ec2.ts"
}
+113
View File
@@ -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;
}
+58
View File
@@ -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}`);
}
}
+21
View File
@@ -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"
}
}
+23
View File
@@ -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"]
}
+79
View File
@@ -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
}
}
+414
View File
@@ -0,0 +1,414 @@
#!/bin/bash
source ~/.bashrc
source aws-tools.sh
source logger.sh
source utils.sh
source stream-tools.sh
cloudGamingLogFile=/tmp/cloud-gaming.log
rm "$cloudGamingLogFile" > /dev/null 2>&1 &
export KERNEL=$(uname -s)
if [[ -z $AWS_CLOUD_GAMING_SSH_KEY ]]; then
printError "The AWS_CLOUD_GAMING_SSH_KEY must be defined in order to use this script." true
exit 1
fi
if [[ $KERNEL == "Darwin" ]]; then
if ! (hash brew 2>/dev/null); then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
fi
if ! (hash whiptail 2>/dev/null); then
printWarn "whiptail is not installed. Installing now..." true
if [[ $KERNEL == "Linux" ]]; then
sudo apt-get -y install whiptail
elif [[ $KERNEL == "Darwin" ]]; then
yes | brew install whiptail
fi
fi
createPrerequisitesMap() {
declare mapName="prerequisites"
declare linuxName="Linux"
declare darwinName="Darwin"
put $mapName "flatpak" $linuxName
put $mapName "xvfb-run" $linuxName
put $mapName "xdotool" $linuxName
put $mapName "dialog" $linuxName
put $mapName "pulsemixer" $linuxName
put $mapName "nc" $linuxName
put $mapName "mas" $darwinName
put $mapName "python" $darwinName
put $mapName "pulseaudio" $darwinName
put $mapName "xdotool" $darwinName
put $mapName "dialog" $darwinName
put $mapName "pulsemixer" $darwinName
put $mapName "nc" $darwinName
}
verifyPrerequisites() {
printInfo "Verifying prerequisites"
declare -a prerequisites
createPrerequisitesMap
prerequisites=($(ls $map/prerequisites/ | xargs -i basename {}))
printInfo "Detected kernel: $KERNEL"
for application in "${prerequisites[@]}"; do
declare value
value=$(get prerequisites "$application")
if [[ ${value[*]} =~ $KERNEL ]] && ! (hash $application 2>/dev/null); then
printWarn "$application is not installed. Installing now..." true
if [[ $KERNEL == "Linux" ]]; then
checkSudoPass "Installing $application requires sudo permissions."
if [[ $application == "xvfb-run" ]]; then
echo "$SUDO_PASSWORD" | sudo -k -S apt-get -y install xvfb
elif [[ $application == "nc" ]]; then
echo "$SUDO_PASSWORD" | sudo -k -S apt-get -y install netcat
else
echo "$SUDO_PASSWORD" | sudo -k -S apt-get -y install $application
fi
elif [[ $KERNEL == "Darwin" ]]; then
if [[ $application == "pulsemixer" ]]; then
pip install pulsemixer
elif [[ $application == "nc" ]]; then
yes | brew install netcat
else
yes | brew install $application
fi
fi
fi
done
if [[ $KERNEL == "Linux" ]]; then
if ! (flatpak info com.valvesoftware.SteamLink > /dev/null 2>&1); then
printWarn "SteamLink is not installed. Installing now..." true
checkSudoPass "Installing SteamLink requires sudo permissions."
printWarn "Installing the FlatHub repo for Flatpak if it doesn't already exist..."
echo "$SUDO_PASSWORD" | sudo -k -S flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
printWarn "Installing SteamLink from FlatHub..."
echo "$SUDO_PASSWORD" | sudo -k -S flatpak install flathub com.valvesoftware.SteamLink
fi
# elif [[ $KERNEL == "Darwin" ]]; then
# TODO check if SteamLink is installed, and if not, install it via mas-cli
fi
if ! (hash aws 2> /dev/null); then
printError "The AWS CLI must be installed to use this script. Please install the applicable AWS CLI from AWS and try again" true
exit 1
fi
if ! (hash npm 2> /dev/null); then
printError "NodeJS and NPM must be installed to use this script. Please install NodeJS and NPM and try again." true
exit 1
fi
if [[ ! -f ~/.ssh/"$AWS_CLOUD_GAMING_SSH_KEY".pem ]]; then
printError "In order to use this script, you need to have the ~/.ssh/$AWS_CLOUD_GAMING_SSH_KEY key. Reach out to one of the team members to acquire it via Snappass and then try again." true
exit 1
fi
checkAwsProfile
}
verifyPrerequisites
checkInstanceStatus() {
declare state
state=$(getInstanceState)
msgBox "Current instance state: $state"
printInfo "Checking instance state. Received state: $state"
}
guideHostThroughSharedStreamSetup() {
printInfo "Guiding user through host shared stream setup"
msgBox "$(cat <<-EOF
This will guide you through getting the other players connected to your personal EC2 instance.
Hit 'OK' to start a desktop connection to your instance
EOF
)"
msgBox "$(cat <<-EOF
We now need to add your user's PINs to your steam client so they can connect to your instance.
Hit 'OK' to start a desktop connection to your steam instance.
EOF
)"
connectToInstanceViaDcvViewer
printInfo "Directing user to enter PIN acquired from user's local SteamLinks"
msgBox "$(cat <<-EOF
On your EC2 Instance, in Steam, navigate to Steam -> Settings -> Remote Play.
Click the 'Pair Steam Link' button and when prompted.
Your users should have this connection PIN ready to give to you, so for each player, provide the PIN you received from them.
Click 'OK' to exit the Steam settings menu when you've entered everyone's PINs and everyone confirms that they see your EC2 instance ready.
Tell your users they can hit 'OK'.
You're now ready to play!
Hit 'OK' to finish the shared instance hosting setup and to start your stream!
EOF
)"
stopStream
startStream
}
guideClientThroughSharedStreamSetup() {
printInfo "Guiding user through client shared stream setup"
printInfo "Starting shared stream for client"
printInfo "Starting SteamLink"
flatpak run com.valvesoftware.SteamLink > /dev/null 2>&1 &
printInfo "Prompting user to fetch the connection PIN from local SteamLink"
msgBox "$(cat <<-EOF
We need to get a unique PIN from SteamLink that will identify this machine for connection to the host's EC2 Instance.
In SteamLink, do the following:
1. Click on the gear icon in the top right hand corner.
2. Then click on 'Computer'
3. Click 'Other Computer'
This will give you a PIN to enter into Steam on the host's EC2 instance.
Give the host this PIN when prompted.
Hit 'OK' when the host says to do so.
EOF
)"
msgBox "$(cat <<-EOF
You should see that EC2 instance highlighted as a streaming option with a big 'Start Playing' button. You're now ready to play!
Finished setting up the shared stream. Have fun!
EOF
)"
}
startSharedStream() {
if (whiptail --fb --title "Shared Stream Setup" --yesno "Are you hosting this shared stream?" --defaultno "$BOX_HEIGHT" "$BOX_WIDTH"); then
printInfo "User is the HOST of the shared stream"
guideHostThroughSharedStreamSetup
else
printInfo "User is a CLIENT of the shared stream"
guideClientThroughSharedStreamSetup
fi
}
streamSettings() {
declare choice
choice=$(whiptail --fb --title "Stream Settings" --menu "Select an option" "$BOX_HEIGHT" "$BOX_WIDTH" 4 \
"P" "Start stream to (p)ersonal instance" \
"S" "Start (S)hared stream" \
"K" "(K)ill the stream" \
"B" "(B)ack" 3>&2 2>&1 1>&3
)
case $choice in
"P")
startStream
streamSettings
;;
"S")
startSharedStream
streamSettings
;;
"K")
stopStream
streamSettings
;;
"B")
mainMenu
;;
esac
}
guideThroughSteamLink() {
printInfo "Guiding user through SteamLink setup"
msgBox "$(cat <<-EOF
Now we need to set up SteamLink.
First, we're going to connect to your instance via the fancy new NICE DCV viewer.
Hit 'OK' when you're ready to log into your instance.
EOF
)"
printInfo "Starting DCV Viewer connection to instance using cloud_gaming_dcv_profile.dcv profile"
waitForInstanceToBecomeAvailable
dcvviewer cloud_gaming_dcv_profile.dcv --certificate-validation-policy=accept-untrusted > /dev/null 2>&1 &
printInfo "Directing user to log into Steam on the instance"
msgBox "$(cat <<-EOF
Next, we need to log into Steam. So start Steam and log into your account.
For ease of use, check the 'Remember my password' box so you don't have to log in manually every time your instance starts.
Hit 'OK' once you're logged in.
EOF
)"
msgBox "$(cat <<-EOF
Next, we need to connect your local SteamLink to this box.
Hit 'OK' to start SteamLink
EOF
)"
printInfo "Starting SteamLink"
flatpak run com.valvesoftware.SteamLink > /dev/null 2>&1 &
printInfo "Prompting user to fetch the connection PIN from local SteamLink"
msgBox "$(cat <<-EOF
Now, we need to get a unique PIN from SteamLink that will identify this machine for connection to your EC2 Instance.
In SteamLink, do the following:
1. Click on the gear icon in the top right hand corner.
2. Then click on 'Computer'
3. Click 'Other Computer'
This will give you a PIN to enter into Steam on your EC2 instance. Hit 'OK' when you're ready to continue.
EOF
)"
printInfo "Directing user to enter PIN acquired from local SteamLink"
msgBox "$(cat <<-EOF
On your EC2 Instance, in Steam, navigate to Steam -> Settings -> Remote Play.
Click the 'Pair Steam Link' button and when prompted, provide the PIN you received from SteamLink in the last step.
Click 'OK' to exit the Steam settings menu.
Once you're done, your SteamLink should have the EC2 instance highlighted. Click on it and it should return you to the main menu.
You should see that EC2 instance highlighted as a streaming option with a big 'Start Playing' button. You're now ready to play!
Hit 'OK' to finish the one-time setup and return to the main menu.
EOF
)"
printInfo "Killing dcvviewer, xvfb (if running, which it shouldn't be), and steamlink"
pkill -9 -f dcvviewer > /dev/null 2>&1 &
pkill -9 -f xvfb > /dev/null 2>&1 &
pkill -9 -f steamlink > /dev/null 2>&1 &
}
deployInstance() {
if (whiptail --fb --title "Deploy CDK" --yesno "This will now deploy the CDK for your cloud gaming instance. Do you wish to continue?" --defaultno "$BOX_HEIGHT" "$BOX_WIDTH"); then
deployCdk
fi
if (whiptail --fb --title "Setup" --yesno "We'll now go through the first time setup. Do you wish to continue?" "$BOX_HEIGHT" "$BOX_WIDTH"); then
msgBox "For first time setups, ensure your terminal is full screen so you don't miss any instructions. If it's not, exit this application, enter full screen, then start again"
if (whiptail --fb --title "First Time Setup" --yesno "This will now perform first time setup for your cloud gaming instance. Is your terminal full screen?" --defaultno "$BOX_HEIGHT" "$BOX_WIDTH"); then
printInfo "Running first time setup"
msgBox "For the first run, some manual, one-time setup steps are required. When ready, hit 'OK' to continue and start the connection to your instance's desktop."
prepareStream
guideThroughSteamLink
msgBox "Finished setting up your instance. Have fun!"
else
printInfo "User selected 'No' to running the first time setup. Nothing was done"
msgBox "Nothing was done."
fi
fi
}
managePersonalInstance() {
declare choice
choice=$(whiptail --fb --title "Manage personal instance" --menu "Select an option" "$BOX_HEIGHT" "$BOX_WIDTH" 7 \
"T" "Check instance s(t)atus" \
"C" "(C)onnect to personal instance desktop via DCV Viewer" \
"I" "D(i)sconnect from personal instance desktop" \
"D" "(D)eploy a personal gaming instance" \
"S" "(S)tart instance" \
"K" "Stop instance" \
"B" "(B)ack" 3>&2 2>&1 1>&3
)
case $choice in
"T")
checkInstanceStatus
managePersonalInstance
;;
"C")
connectToInstanceViaDcvViewer
managePersonalInstance
;;
"I")
printInfo "Killing connection to instance desktop via DCV Viewer"
pkill -9 -f dcvviewer > /dev/null 2>&1 &
managePersonalInstance
;;
"D")
deployInstance
managePersonalInstance
;;
"S")
startInstance
managePersonalInstance
;;
"K")
stopInstance
managePersonalInstance
;;
"B")
mainMenu
;;
esac
}
mainMenu() {
declare choice
choice=$(whiptail --fb --title "Team Building Cloud Gaming" --menu "Select an option" "$BOX_HEIGHT" "$BOX_WIDTH" 3 \
"M" "(M)anage your personal instance" \
"S" "(S)tream settings" \
"X" "E(x)it" 3>&2 2>&1 1>&3
)
case $choice in
"M")
managePersonalInstance
;;
"S")
streamSettings
;;
"X")
clear
printInfo "Killing dcvviewer, xvfb, and steamlink"
pkill -9 -f dcvviewer > /dev/null 2>&1 &
pkill -9 -f xvfb > /dev/null 2>&1 &
pkill -9 -f steamlink > /dev/null 2>&1 &
if (whiptail --fb --title "Stop instance" --yesno "Do you wish to stop your instance before exiting?" "$BOX_HEIGHT" "$BOX_WIDTH"); then
stopInstance
fi
printInfo "Exiting"
exit 0
;;
esac
}
while :; do
mainMenu
done
+12
View File
@@ -0,0 +1,12 @@
[connect]
host=PLACEHOLDER
port=8443
user=Administrator
password=PLACEHOLDER
weburlpath=
[version]
format=1.0
[input]
enable-relative-mouse=false
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
red=$(tput setaf 1)
green=$(tput setaf 2)
gold=$(tput setaf 3)
blue=$(tput setaf 4)
magenta=$(tput setaf 5)
cyan=$(tput setaf 6)
gray=$(tput setaf 243)
default=$(tput sgr0)
bold=$(tput bold)
printError() {
if [[ -z $2 ]]; then
echo -e "${red}${bold}ERROR:${default}${red} $1${default}" >> $cloudGamingLogFile
else
echo -e "${red}${bold}ERROR:${default}${red} $1${default}"
echo -e "${red}${bold}ERROR:${default}${red} $1${default}" >> $cloudGamingLogFile
fi
}
printWarn() {
if [[ -z $2 ]]; then
echo -e "${gold}${bold}WARN:${default}${gold} $1${default}" >> $cloudGamingLogFile
else
echo -e "${gold}${bold}WARN:${default}${gold} $1${default}"
echo -e "${gold}${bold}WARN:${default}${gold} $1${default}" >> $cloudGamingLogFile
fi
}
printInfo() {
if [[ -z $2 ]]; then
echo -e "${cyan}${bold}INFO:${default}${cyan} $1${default}" >> $cloudGamingLogFile
else
echo -e "${cyan}${bold}INFO:${default}${cyan} $1${default}"
echo -e "${cyan}${bold}INFO:${default}${cyan} $1${default}" >> $cloudGamingLogFile
fi
}
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
source aws-tools.sh
source logger.sh
source utils.sh
prepareStream() {
printInfo "Preparing stream and configuring NICE DCV Viewer"
checkSudoPass "Installing NICE DCV Client requires sudo permissions."
declare architecture
architecture=$(uname -p)
printInfo "Detected architecture: $architecture"
{
echo -e "XXX\n0\nInstalling NICE DCV Client... \nXXX"
printInfo "Installing NICE DCV Viewer client"
# if [[ $kernel == "Linux" ]]; then
wget -o nice-dcv-viewer.deb https://d1uj6qtbmh3dt5.cloudfront.net/2022.1/Clients/nice-dcv-viewer_2022.1.4251-1_amd64.ubuntu2004.deb > /dev/null 2>&1
echo "$SUDO_PASSWORD" | sudo -k -S sh -c "dpkg -i nice-dcv-viewer.deb" > /dev/null 2>&1
# elif [[ $kernel == "Darwin" ]]; then
# if [[ $architecture == "x86_64" ]]; then
# wget -o nice-dcv-viewer.dmg https://d1uj6qtbmh3dt5.cloudfront.net/2022.1/Clients/nice-dcv-viewer-2022.1.4279.x86_64.dmg > /dev/null 2>&1
# elif [[ $architecture == "arm64" ]]; then
# wget -o nice-dcv-viewer.dmg https://d1uj6qtbmh3dt5.cloudfront.net/2022.1/Clients/nice-dcv-viewer-2022.1.4279.arm64.dmg > /dev/null 2>&1
# fi
#
# TODO figure out how to install dcvviewer and how to run it on a Mac
# echo "$SUDO_PASSWORD" | sudo -k -S sh -c "sudo hdiutil attach nice-dcv-viewer.dmg"
# fi
echo -e "XXX\n33\nCleaning up... \nXXX"
printInfo "Removing downloaded DCV Viewer installation"
rm nice-dcv-viewer* > /dev/null 2>&1
echo -e "XXX\n66\nCreating Connection Profile from template... \nXXX"
createDcvConnectionProfileFromTemplate
echo -e "XXX\n100\nDone! \nXXX"
sleep 1
} | whiptail --title "Installing NICE DCV Client..." --gauge "Installing NICE DCV Client..." "$GAUGE_BOX_HEIGHT" "$GAUGE_BOX_WIDTH" 0
}
startStream() {
printInfo "Starting stream"
if ! (hash dcvviewer 2> /dev/null); then
printError "Unable to start stream: dcvviewer is not installed"
msgBox "Can't stream without first time setup. Run the deploy instance task from the instance management menu first!"
return 0
fi
if (pgrep -f dcvviewer || pgrep -f steam); then
printError "Stream is already running."
msgBox "Stream is already running"
return 0
fi
declare state
state=$(getInstanceState)
if [[ $state != "running" ]]; then
if (whiptail --fb --title "Start your instance?" --yesno "In order to stream, you instance needs to be started. Would you like to start your personal instance?" "$BOX_BOX_HEIGHT" "$BOX_WIDTH"); then
startInstance
else
printError "Unable to start stream: Instance is not running"
msgBox "Can't stream! Instance is not running. You can start the instance from the Instance Management menu."
return 0
fi
fi
printInfo "Starting dcvviewer in background"
xvfb-run -a dcvviewer cloud_gaming_dcv_profile.dcv --certificate-validation-policy=accept-untrusted > /dev/null 2>&1 &
sleep 0.25
printInfo "Minimizing active window"
xdotool windowminimize $(xdotool getactivewindow)
printInfo "Muting DCV Viewer"
pulsemixer --toggle-mute --id $(pulsemixer -l | grep dcvviewer | awk '{ print $4; }' | sed 's/,//g') > /dev/null 2>&1
sleep 0.25
printInfo "Starting SteamLink"
flatpak run com.valvesoftware.SteamLink > /dev/null 2>&1 &
}
stopStream() {
printInfo "Stopping the stream"
pkill -9 -f dcvviewer > /dev/null 2>&1 &
pkill -9 -f xvfb > /dev/null 2>&1 &
pkill -9 -f steamlink > /dev/null 2>&1 &
printInfo "Stopped the stream"
msgBox "Successfully killed the stream"
}
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
source logger.sh
TERMINAL_HEIGHT=$(tput lines)
BOX_HEIGHT=$(printf "%.0f" "$(echo "scale=2; $TERMINAL_HEIGHT * .5" | bc)")
GAUGE_BOX_HEIGHT=$(printf "%.0f" "$(echo "scale=2; $TERMINAL_HEIGHT * .25" | bc)")
TERMINAL_WIDTH=$(tput cols)
BOX_WIDTH=$(printf "%.0f" "$(echo "scale=2; $TERMINAL_WIDTH * .75" | bc)")
GAUGE_BOX_WIDTH=$(printf "%.0f" "$(echo "scale=2; $TERMINAL_WIDTH * .5" | bc)")
setConfigValue() {
printInfo "Setting bashrc environment variable: $1=$2"
if ( grep "$1" ~/.bashrc ); then
sed -i "/$1=/c\export $1=$2" ~/.bashrc
else
echo "export $1=$2" >> ~/.bashrc
fi
unset "$1"
printf -v "$1" '%s' "$2"
}
msgBox() {
whiptail --fb --msgbox "$1" "$BOX_HEIGHT" "$BOX_WIDTH"
}
showTailBox() {
trap "kill $2 2> /dev/null" EXIT
while kill -0 "$2" 2> /dev/null; do
dialog --title "$1" --exit-label "Finished" --tailbox "$3" "$BOX_HEIGHT" "$BOX_WIDTH"
done
clear
trap - EXIT
}
checkSudoPass() {
printInfo "Prompting user for sudo password with message: $1"
if [[ ! "$SUDO_PASSWORD" ]]; then
SUDO_PASSWORD=$(whiptail --passwordbox "$1 Enter your sudo password" "$BOX_HEIGHT" "$BOX_WIDTH" 3>&2 2>&1 1>&3)
fi
}
createMap() {
declare prefix
prefix=$(basename -- "$0")
map=$(mktemp -dt "$prefix.XXXXXXXX")
trap "rm -rf $map" EXIT
}
put() {
declare mapName="$1"
declare key="$2"
declare value="$3"
printInfo "Adding [$key: $value] to map $mapName"
[[ -z $map ]] && createMap
[[ -d "$map/$mapName" ]] || mkdir "$map/$mapName"
echo "$value" >> "$map/$mapName/$key"
}
get() {
declare mapName="$1"
declare key="$2"
[[ -z $map ]] && createMap
cat "$map/$mapName/$key"
printInfo "Fetched $map/$mapName/$key: $(cat $map/$mapName/$key)"
}