From aba5cd66160e0f79ca4adf662ce3a0481ee69de9 Mon Sep 17 00:00:00 2001 From: Andrew Marcum <123010092+abmarcum@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:47:14 -0600 Subject: [PATCH] Add initial Game Client VM Terraform (#224) * Added iniital Game Client VM Terraform * Additional changes to the Game Client VM setup * Changed to download & install Moonlight/Sunshine Server * Cleaned up VM app installs & added xpra * Added xpra signing key for apt * Added Game Client VM instructions * Added more Game Client VM instructions * Added Nvidia drivers and Chrome Remote Desktop * Removed unneeded commented line --------- Co-authored-by: Mark Mandel --- README.md | 24 ++++- infrastructure/game-client-startup.sh | 95 ++++++++++++++++++ infrastructure/game-client.tf | 134 +++++++++++++++++++++++++ infrastructure/game-server.tf | 2 +- infrastructure/services-gke.tf | 4 + infrastructure/terraform.tfvars.sample | 9 ++ infrastructure/variables.tf | 38 +++++++ 7 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 infrastructure/game-client-startup.sh create mode 100644 infrastructure/game-client.tf diff --git a/README.md b/README.md index b21c9d2..c772593 100644 --- a/README.md +++ b/README.md @@ -317,8 +317,30 @@ gcloud compute addresses list --filter=name=frontend-service --format="value(add JWT token can be obtained by accessing frontend api's ip address with '/login' path, such as "http://[IP_ADDRESS].sslip.io/login" and extracting it from the URL. +### Enable Cloud Linux VM for Game Client -#### Run the Game Launcher +You have the option to enable a GCP Linux VM for the Game Client. To have Terraform setup the VM, edit `terraform.tfvars` and set: + +`enable_game_client_vm = true` + +Then you will need to run Terraform to apply the changes to your environment: + +```shell +terraform apply +``` + +Once Terraform has run and a few minutes have passed for all of the software packages to install, you can then connect to the VM using gcloud: + +```shell +gcloud compute ssh game-client-vm +``` + +You can also connect to an X display using [Chrome Remote Desktop](https://remotedesktop.google.com/). +The first time you use the service, you will need to configure the Game Client VM by [Setting up via SSH](https://remotedesktop.google.com/headless) + +You can click through the buttons Begin -> Next -> Authorize to get to the screen where you can then copy the commands for `Debian Linux`. Then you can paste these commands into the SSH terminal you have open to the Game Client VM. After the commands have been succesfully run, you can return to [Chrome Remote Desktop](https://remotedesktop.google.com/access) and click on the Game Client VM that should now be displayed. + +### Run the Game Launcher To run the game launcher, you will need to have [Go](https://go.dev/dl/) installed to run it, as well as the [prerequisites for the Fyne Go Cross Platform UI library](https://developer.fyne.io/started/). diff --git a/infrastructure/game-client-startup.sh b/infrastructure/game-client-startup.sh new file mode 100644 index 0000000..48dbc3e --- /dev/null +++ b/infrastructure/game-client-startup.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Script to install applications for the Game Client VM + +export DEBIAN_FRONTEND=noninteractive + +wget -O "/usr/share/keyrings/xpra.asc" https://xpra.org/xpra.asc +cd /etc/apt/sources.list.d +wget https://raw.githubusercontent.com/Xpra-org/xpra/master/packaging/repos/bookworm/xpra.sources +sudo apt update -y +cd - + +curl -OL https://github.com/LizardByte/Sunshine/releases/download/v0.21.0/sunshine-debian-bookworm-amd64.deb +sudo apt install -f ./sunshine-debian-bookworm-amd64.deb -y +sudo apt install xfce4 xfce4-goodies tightvncserver dbus-x11 gcc make linux-headers-$(uname -r) software-properties-common mesa-utils golang libgl1-mesa-dev xorg-dev xpra -y + +wget https://developer.download.nvidia.com/compute/cuda/12.3.1/local_installers/cuda_12.3.1_545.23.08_linux.run +sudo sh cuda_12.3.1_545.23.08_linux.run --silent + +sudo apt update -y + +######### +# +# Startup script to install Chrome remote desktop and a desktop environment. +# +# See environmental variables at then end of the script for configuration +# + +function install_desktop_env { + PACKAGES="desktop-base xscreensaver dbus-x11" + + if [[ "$INSTALL_XFCE" != "yes" && "$INSTALL_CINNAMON" != "yes" ]] ; then + # neither XFCE nor cinnamon specified; install both + INSTALL_XFCE=yes + INSTALL_CINNAMON=yes + fi + + if [[ "$INSTALL_XFCE" = "yes" ]] ; then + PACKAGES="$PACKAGES xfce4" + echo "exec xfce4-session" > /etc/chrome-remote-desktop-session + [[ "$INSTALL_FULL_DESKTOP" = "yes" ]] && \ + PACKAGES="$PACKAGES task-xfce-desktop" + fi + + if [[ "$INSTALL_CINNAMON" = "yes" ]] ; then + PACKAGES="$PACKAGES cinnamon-core" + echo "exec cinnamon-session-cinnamon2d" > /etc/chrome-remote-desktop-session + [[ "$INSTALL_FULL_DESKTOP" = "yes" ]] && \ + PACKAGES="$PACKAGES task-cinnamon-desktop" + fi + + DEBIAN_FRONTEND=noninteractive \ + apt-get install --assume-yes $PACKAGES $EXTRA_PACKAGES + + systemctl disable lightdm.service +} + +function download_and_install { # args URL FILENAME + curl -L -o "$2" "$1" + apt-get install --assume-yes --fix-broken "$2" +} + +function is_installed { # args PACKAGE_NAME + dpkg-query --list "$1" | grep -q "^ii" 2>/dev/null + return $? +} + +# Configure the following environmental variables as required: +INSTALL_XFCE=yes +INSTALL_CINNAMON=yes +INSTALL_CHROME=yes +INSTALL_FULL_DESKTOP=yes + +# Any additional packages that should be installed on startup can be added here +EXTRA_PACKAGES="less bzip2 zip unzip tasksel wget" + +apt-get update + +! is_installed chrome-remote-desktop && \ + download_and_install \ + https://dl.google.com/linux/direct/chrome-remote-desktop_current_amd64.deb \ + /tmp/chrome-remote-desktop_current_amd64.deb + +install_desktop_env + +[[ "$INSTALL_CHROME" = "yes" ]] && \ + ! is_installed google-chrome-stable && \ + download_and_install \ + https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ + /tmp/google-chrome-stable_current_amd64.deb + +echo "Chrome remote desktop installation completed" + +# Delete instance startup script do it does not re-run after a boot +gcloud compute instances remove-metadata --keys startup-script --zone us-central1-a game-client-vm diff --git a/infrastructure/game-client.tf b/infrastructure/game-client.tf new file mode 100644 index 0000000..425ea56 --- /dev/null +++ b/infrastructure/game-client.tf @@ -0,0 +1,134 @@ +# Copyright 2023 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +resource "google_service_account" "game_client_vm" { + count = var.enable_game_client_vm ? 1 : 0 + + project = var.project + + account_id = "game-client-vm" + display_name = "Custom SA for Game Client VM" +} + +resource "google_project_iam_member" "game_client_vm_compute_admin" { + count = var.enable_game_client_vm ? 1 : 0 + + project = var.project + role = "roles/compute.instanceAdmin.v1" + member = "serviceAccount:${google_service_account.game_client_vm[0].email}" +} + +resource "google_project_iam_member" "game_client_vm_compute_is_sa" { + count = var.enable_game_client_vm ? 1 : 0 + + project = var.project + role = "roles/iam.serviceAccountUser" + member = "serviceAccount:${google_service_account.game_client_vm[0].email}" +} + +resource "google_compute_address" "game_client_vm_static_ip" { + count = var.enable_game_client_vm ? 1 : 0 + + project = var.project + name = "game-client-vm-static-ip" + region = var.game_client_vm_region +} + +data "google_compute_image" "game_client_vm_os" { + count = var.enable_game_client_vm ? 1 : 0 + + family = var.game_client_vm_os_family + project = var.game_client_vm_os_project +} + +resource "google_compute_instance" "game_client_vm" { + count = var.enable_game_client_vm ? 1 : 0 + + project = var.project + + name = "game-client-vm" + machine_type = var.game_client_vm_machine_type + zone = "${var.game_client_vm_region}-a" + + tags = ["game-client-vm-ssh", "game-client-vm-vnc"] + + scheduling { + on_host_maintenance = "TERMINATE" + } + + boot_disk { + initialize_params { + size = var.game_client_vm_storage + image = data.google_compute_image.game_client_vm_os[0].self_link + } + } + + // Local SSD disk + scratch_disk { + interface = "NVME" + } + + network_interface { + subnetwork = google_compute_subnetwork.subnet["${var.game_client_vm_region}"].self_link + # network = google_compute_network.vpc.id + + access_config { + // Ephemeral public IP + nat_ip = google_compute_address.game_client_vm_static_ip[0].address + } + } + + metadata = { + serial-port-logging-enable = "TRUE" + } + + metadata_startup_script = file("${path.root}/game-client-startup.sh") + + service_account { + # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles. + email = google_service_account.game_client_vm[0].email + scopes = ["cloud-platform"] + } +} + +resource "google_compute_firewall" "game-client-vm-ssh" { + project = var.project + + name = "game-client-vm-ssh" + network = google_compute_network.vpc.id + + allow { + protocol = "tcp" + ports = ["22"] + } + + target_tags = ["game-client-vm-ssh"] + source_ranges = var.game_client_vm_allowed_cidr +} + +resource "google_compute_firewall" "game-client-vm-vnc" { + project = var.project + + name = "game-client-vm-vnc" + network = google_compute_network.vpc.id + + allow { + protocol = "tcp" + ports = ["5901"] + } + + target_tags = ["game-client-vm-vnc"] + source_ranges = var.game_client_vm_allowed_cidr +} diff --git a/infrastructure/game-server.tf b/infrastructure/game-server.tf index 70b31cb..2eae969 100644 --- a/infrastructure/game-server.tf +++ b/infrastructure/game-server.tf @@ -36,6 +36,6 @@ resource "google_secret_manager_secret_iam_binding" "cloud_build_binding" { secret_id = google_secret_manager_secret.secret_github_packages.id role = "roles/secretmanager.secretAccessor" members = [ - "serviceAccount:cloudbuild-cicd@${var.project}.iam.gserviceaccount.com", + google_service_account.cloudbuild-sa.member ] } diff --git a/infrastructure/services-gke.tf b/infrastructure/services-gke.tf index f076715..29eba39 100644 --- a/infrastructure/services-gke.tf +++ b/infrastructure/services-gke.tf @@ -19,6 +19,10 @@ resource "google_container_cluster" "services-gke" { network = google_compute_network.vpc.name subnetwork = google_compute_subnetwork.subnet[var.services_gke_config.location].name + gateway_api_config { + channel = "CHANNEL_STANDARD" + } + # See issue: https://github.com/hashicorp/terraform-provider-google/issues/10782 ip_allocation_policy {} diff --git a/infrastructure/terraform.tfvars.sample b/infrastructure/terraform.tfvars.sample index 71cce2d..f6fa21e 100644 --- a/infrastructure/terraform.tfvars.sample +++ b/infrastructure/terraform.tfvars.sample @@ -18,6 +18,15 @@ project = "PROJECT_ID" resource_env_label = "demo-global-game" +# Game Client Configuration +enable_game_client_vm = false +game_client_vm_machine_type = "g2-standard-4" +game_client_vm_region = "us-central1" # MUST MATCH one of the below VPC regions +game_client_vm_storage = 300 +game_client_vm_os_family = "debian-12" +game_client_vm_os_project = "debian-cloud" +game_client_vm_allowed_cidr = ["0.0.0.0/0"] + # Cloud Deploy Configuration platform_directory = "../platform" # Relative to Terraform directory services_directory = "../services" # Relative to Terraform directory diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index b898cdd..3f70119 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -154,3 +154,41 @@ variable "github_pat" { type = string description = "A GitHub personal access token (classic) with at least repo scope" } + +### Game Client VM Variables + +variable "enable_game_client_vm" { + type = bool + description = "Whether to create or not a Linux Game Client VM" + default = false +} + +variable "game_client_vm_machine_type" { + type = string + description = "Game Client VM Machine Type" +} + +variable "game_client_vm_allowed_cidr" { + type = list(any) + description = "Game Client VM Allowed CIDRs" +} + +variable "game_client_vm_region" { + type = string + description = "Game Client VM Region" +} + +variable "game_client_vm_storage" { + type = number + description = "Game Client VM Storage Size" +} + +variable "game_client_vm_os_family" { + type = string + description = "Game Client VM OS Image Family" +} + +variable "game_client_vm_os_project" { + type = string + description = "Game Client OS Image Project" +}