diff --git a/README.md b/README.md index 861a518..21ff305 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EC2 Gaming on Linux -Cloud Gaming powered by Ubuntu Linux and [Sunshine] running on EC2 Spot Instances, tested with +Cloud Gaming powered by [Sunshine] on EC2 Spot Instances, tested with: * EC2 g4dn instances using NVIDIA gaming driver * EC2 g5 instances using NVIDIA gaming driver @@ -34,7 +34,10 @@ Launch spot instance: Launch on-demand instance: + # ubuntu jammy (22.04) aws ec2 run-instances --launch-template LaunchTemplateName=ec2-gaming-sunshine-jammy-on-demand,Version=\$Latest + # or: debian bookworm (12) + aws ec2 run-instances --launch-template LaunchTemplateName=ec2-gaming-sunshine-bookworm-on-demand,Version=\$Latest Launch on-demand instance with custom instance type: @@ -49,9 +52,12 @@ By default, access to the EC2 instance is restriced. To update the whitelisted I ### Install NVIDIA driver -Login to EC2 instance: +Login to the EC2 instance: + # for ubuntu (jammy) instances ssh ubuntu@ + # for debian (bookworm) instances + ssh admin@ Wait for cloud-init to finish: @@ -69,11 +75,28 @@ Configure username and password for sunshine API user: https --verify=no :47990/api/password newUsername="sunshine" newPassword="sunshine" confirmNewPassword="sunshine" +Add apps for different screen resolutions: + + # 1280x720 + https --verify=no -a sunshine:sunshine :47990/api/apps \ + name="1280x720" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1280x720","undo":""}]' \ + output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" + + # 1280x800 + https --verify=no -a sunshine:sunshine :47990/api/apps \ + name="1280x800" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1280x800","undo":""}]' \ + output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" + + # 1920x1080 + https --verify=no -a sunshine:sunshine :47990/api/apps \ + name="1920x1080" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1920x1080","undo":""}]' \ + output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" + Set a password for `sunshine` Linux user: sudo passwd sunshine -Determine public IPv4 address and connect from a [Moonlight] client: +Determine the public IPv4 address and connect via the [Moonlight] client: cloud-init query ds.meta_data.public_ipv4 @@ -87,22 +110,6 @@ Launch Steam, Login for the first time and: * Enable Steam Play (Proton) for supported and all other titles (Setting/Steam Play) * Run Backup (via application icon or `/usr/local/bin/backup` before next shutdown/reboot) -## Optional steps - -Add apps for different screen resolutions: - - https --verify=no -a sunshine:sunshine :47990/api/apps \ - name="1280x720" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1280x720","undo":""}]' \ - output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" - - https --verify=no -a sunshine:sunshine :47990/api/apps \ - name="1280x800" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1280x800","undo":""}]' \ - output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" - - https --verify=no -a sunshine:sunshine :47990/api/apps \ - name="1920x1080" prep-cmd:='[{"do":"xrandr --output DVI-D-0 --mode 1920x1080","undo":""}]' \ - output="" cmd:=[] index=-1 detached:=[] image-path="desktop-alt.png" - [cloud-init]: https://cloudinit.readthedocs.io/ [Lutris]: https://lutris.net [Moonlight]: https://github.com/moonlight-stream/moonlight-qt/ diff --git a/cloud-init/cloud-config-bookworm.yaml b/cloud-init/cloud-config-bookworm.yaml new file mode 100644 index 0000000..e376fe3 --- /dev/null +++ b/cloud-init/cloud-config-bookworm.yaml @@ -0,0 +1,216 @@ +#cloud-config + +bootcmd: + - dpkg --add-architecture i386 + - wget http://repo.steampowered.com/steam/archive/stable/steam.gpg -P /usr/share/keyrings/ + +apt: + preserve_sources_list: false + sources: + steam-stable.list: + source: | + deb [arch=amd64,i386 signed-by=/usr/share/keyrings/steam.gpg] https://repo.steampowered.com/steam/ stable steam + deb-src [arch=amd64,i386 signed-by=/usr/share/keyrings/steam.gpg] https://repo.steampowered.com/steam/ stable steam + conf: | + APT { + Install-Suggests "0"; + Install-Recommends "0"; + } + +package_update: true +packages: +- adwaita-icon-theme-full +- alsa-utils +- awscli +- baobab +- build-essential +- dkms +- dmz-cursor-theme +- gdm3 +- gedit +- gnome-control-center +- gnome-session +- gnome-shell-extension-appindicator +- gnome-terminal +- httpie +- libc6-dev +- libcanberra-pulse +- libglvnd-dev +- libva-drm2 +- libva2 +- libvdpau1 +- libvulkan1 +- linux-headers-cloud-amd64 +- linux-image-cloud-amd64 +- nautilus +- pkg-config +- pulseaudio +- pulseaudio-module-gsettings +- restic +- steam-launcher +- steam-libs-amd64 +- unzip +- xdg-desktop-portal-gtk +- xdg-utils +- xserver-xorg-core +- xserver-xorg-dev +- xserver-xorg-input-libinput + +# required on first boot only +fs_setup: +- label: instance_storage + device: /dev/nvme1n1 + filesystem: ext4 + +mounts: +- ["/dev/nvme1n1", "/mnt", "ext4", "defaults,nofail,x-systemd.requires=cloud-init.service,x-systemd.makefs", "0", "2"] + +users: +- default +- name: sunshine + gecos: Sunshine + groups: users, audio, video, plugdev, netdev, input + shell: /bin/bash + +write_files: +- path: /etc/modprobe.d/blacklist.conf + content: | + blacklist vga16fb + blacklist nouveau + blacklist rivafb + blacklist nvidiafb + blacklist rivatv +- path: /etc/modprobe.d/nvidia.conf + content: | + options nvidia NVreg_EnableGpuFirmware=0 +- path: /etc/gdm3/daemon.conf + content: | + [daemon] + WaylandEnable = false + AutomaticLoginEnable = true + AutomaticLogin = sunshine + [security] + [xdmcp] +- path: /etc/udev/rules.d/85-sunshine-input.rules + content: | + KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" +- path: /opt/sunshine_systemd_user_service.patch + content: | + 5,7c5 + < PartOf=graphical-session.target + < Wants=xdg-desktop-autostart.target + < After=xdg-desktop-autostart.target + --- + > After=graphical-session.target + 15c13 + < WantedBy=xdg-desktop-autostart.target + --- + > WantedBy=graphical-session.target +- path: /opt/install_nvidia_driver.sh + content: | + #!/bin/bash + set -euo pipefail + cd /mnt + aws s3 cp --recursive s3://nvidia-gaming/linux/latest/ . + unzip *Cloud_Gaming-Linux-Guest-Drivers.zip -d nvidia-drivers + rm -f *Cloud_Gaming-Linux-Guest-Drivers.zip + chmod +x ./nvidia-drivers/*Cloud_Gaming-Linux-Guest-Drivers/NVIDIA-Linux-x86_64*-grid.run + /bin/sh ./nvidia-drivers/*Cloud_Gaming-Linux-Guest-Drivers/NVIDIA-Linux-x86_64*-grid.run --dkms --tmpdir=/mnt --silent + rm -rf ./nvidia-drivers/ + kversion=$(linux-version list |sort |tail -n1) + dkms autoinstall -k $kversion + update-initramfs -k $kversion -u + cat << EOF | tee -a /etc/nvidia/gridd.conf + vGamingMarketplace=2 + EOF + curl -o /etc/nvidia/GridSwCert.txt "https://nvidia-gaming.s3.amazonaws.com/GridSwCert-Archive/GridSwCertLinux_2021_10_2.cert" + nvidia-xconfig --preserve-busid --enable-all-gpus + nvidia-smi -q | head + permissions: '0755' +- path: /etc/systemd/system/init_mnt.service + content: | + [Unit] + After = network-online.target + Wants = network-online.target + [Service] + Type = oneshot + RemainAfterExit = yes + ExecStart = mkdir -p /mnt/sunshine + ExecStart = chown sunshine:sunshine /mnt/sunshine + [Install] + WantedBy = multi-user.target +- path: /home/admin/.bash_history + content: | + sudo reboot + sudo /opt/install_nvidia_driver.sh + tail -f /var/log/cloud-init.log + owner: admin:admin + permissions: '0600' + defer: true +- path: /usr/lib/systemd/user/init_desktop_settings.service + content: | + [Unit] + Description=Init desktop settings + [Service] + Type = oneshot + RemainAfterExit = yes + ExecStart=/usr/bin/gnome-extensions enable ubuntu-appindicators@ubuntu.com + ExecStart=/usr/bin/gsettings set org.gnome.desktop.background picture-uri "file:///usr/share/backgrounds/gnome/adwaita-l.webp" + ExecStart=/usr/bin/gsettings set org.gnome.desktop.interface enable-hot-corners false + [Install] + WantedBy=graphical-session.target + permissions: '0644' +- path: /etc/profile.d/restic.sh + content: | + #!/bin/sh + export RESTIC_REPOSITORY=s3:https://s3.eu-central-1.amazonaws.com/ec2-gaming-sunshine-library + export RESTIC_PASSWORD=sunshine + permissions: '0644' +- path: /usr/local/bin/backup + content: | + #!/bin/sh + restic snapshots || restic init + restic backup /mnt/sunshine + permissions: '0755' + defer: true +- path: /usr/local/bin/restore + content: | + #!/bin/sh + restic snapshots && \ + restic restore latest -v --target / --host $(hostname -s) + read -p "press any key" + permissions: '0755' + defer: true +- path: /usr/local/share/applications/restore.desktop + content: | + [Desktop Entry] + Type=Application + Name=Restore + Exec=/usr/local/bin/restore + Terminal=true + permissions: '0644' + defer: true +- path: /usr/local/share/applications/backup.desktop + content: | + [Desktop Entry] + Type=Application + Name=Backup + Exec=/usr/local/bin/backup + Terminal=true + permissions: '0644' + defer: true + +runcmd: + - systemctl enable init_mnt + - rm -f /etc/apt/sources.list.d/steam.list + - apt update + - apt install -y libgl1-mesa-dri:i386 steam-libs-i386:i386 libgl1-mesa-dri:amd64 libgl1-mesa-glx:i386 libgl1-mesa-glx:amd64 libvulkan1:i386 libgnutls30:i386 + - wget https://github.com/LizardByte/Sunshine/releases/download/v0.23.1/sunshine-debian-bookworm-amd64.deb + - apt install -y ./sunshine-debian-bookworm-amd64.deb + - rm -f ./sunshine-debian-bookworm-amd64.deb + - patch /usr/lib/systemd/user/sunshine.service < /opt/sunshine_systemd_user_service.patch + - su -c "systemctl --user enable sunshine" sunshine + - su -c "systemctl --user enable init_desktop_settings" sunshine + - su -c "desktop-file-install --dir=/home/sunshine/.config/autostart /usr/local/share/applications/restore.desktop" sunshine + - wget https://github.com/lutris/lutris/releases/download/v0.5.14/lutris_0.5.14_all.deb + - apt-get install -y ./lutris_0.5.14_all.deb diff --git a/cloudformation/ec2-gaming-sunshine.yaml b/cloudformation/ec2-gaming-sunshine.yaml index 8bca6f4..78857ce 100644 --- a/cloudformation/ec2-gaming-sunshine.yaml +++ b/cloudformation/ec2-gaming-sunshine.yaml @@ -10,6 +10,9 @@ Parameters: LatestAmiIdJammy: Type: "AWS::SSM::Parameter::Value" Default: "/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id" + LatestAmiIdBookworm: + Type: "AWS::SSM::Parameter::Value" + Default: "/aws/service/debian/release/bookworm/latest/amd64" Resources: InstanceRole: @@ -154,6 +157,93 @@ Resources: FromPort: 27036 ToPort: 27037 CidrIp: 0.0.0.0/0 + LaunchTemplateBookwormSpot: + Type: "AWS::EC2::LaunchTemplate" + Properties: + LaunchTemplateName: !Sub "${AWS::StackName}-bookworm-spot" + LaunchTemplateData: + IamInstanceProfile: + Arn: !GetAtt InstanceProfile.Arn + NetworkInterfaces: + - DeviceIndex: 0 + AssociatePublicIpAddress: true + DeleteOnTermination: true + SubnetId: !Ref Subnet1 + Groups: + - !GetAtt AdminAccess.GroupId + - !GetAtt GamingAccess.GroupId + - !GetAtt RemotePlayAccess.GroupId + PrivateDnsNameOptions: + HostnameType: resource-name + BlockDeviceMappings: + - Ebs: + VolumeSize: 20 + VolumeType: gp2 + DeleteOnTermination: true + Encrypted: true + DeviceName: /dev/xvda + ImageId: !Ref LatestAmiIdBookworm + InstanceType: g4dn.xlarge + KeyName: !Ref KeyPair + InstanceMarketOptions: + MarketType: spot + SpotOptions: + InstanceInterruptionBehavior: stop + SpotInstanceType: persistent + TagSpecifications: + - ResourceType: instance + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-instance" + - Key: Distribution + Value: Debian + - Key: Release + Value: 12 + - Key: Codename + Value: bookworm + UserData: | + {{ cloud_config["bookworm"] }} + LaunchTemplateBookwormOnDemand: + Type: "AWS::EC2::LaunchTemplate" + Properties: + LaunchTemplateName: !Sub "${AWS::StackName}-bookworm-on-demand" + LaunchTemplateData: + IamInstanceProfile: + Arn: !GetAtt InstanceProfile.Arn + NetworkInterfaces: + - DeviceIndex: 0 + AssociatePublicIpAddress: true + DeleteOnTermination: true + SubnetId: !Ref Subnet1 + Groups: + - !GetAtt AdminAccess.GroupId + - !GetAtt GamingAccess.GroupId + - !GetAtt RemotePlayAccess.GroupId + PrivateDnsNameOptions: + HostnameType: resource-name + BlockDeviceMappings: + - Ebs: + VolumeSize: 20 + VolumeType: gp2 + DeleteOnTermination: true + Encrypted: true + DeviceName: /dev/xvda + ImageId: !Ref LatestAmiIdBookworm + InstanceType: g4dn.xlarge + KeyName: !Ref KeyPair + TagSpecifications: + - ResourceType: instance + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-instance" + - Key: Distribution + Value: Debian + - Key: Release + Value: 12 + - Key: Codename + Value: bookworm + UserData: | + {{ cloud_config["bookworm"] }} LaunchTemplateJammySpot: Type: "AWS::EC2::LaunchTemplate" Properties: diff --git a/deploy.py b/deploy.py index 5334e0a..a221f4a 100755 --- a/deploy.py +++ b/deploy.py @@ -26,7 +26,7 @@ def main(): template = Template(cf_.read()) cloud_config = {} - for flavour in ["jammy"]: + for flavour in ["jammy", "bookworm"]: with open(f"cloud-init/cloud-config-{flavour}.yaml", "rb") as cloud_config_: cloud_config_b64_ = base64.b64encode(cloud_config_.read()) cloud_config[flavour] = cloud_config_b64_.decode("ascii")