Open-sourced my personal cloud-gaming scripts
This commit is contained in:
+28
@@ -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
|
||||||
@@ -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
@@ -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 &
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+414
@@ -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
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[connect]
|
||||||
|
host=PLACEHOLDER
|
||||||
|
port=8443
|
||||||
|
user=Administrator
|
||||||
|
password=PLACEHOLDER
|
||||||
|
weburlpath=
|
||||||
|
|
||||||
|
[version]
|
||||||
|
format=1.0
|
||||||
|
|
||||||
|
[input]
|
||||||
|
enable-relative-mouse=false
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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)"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user