AWSTemplateFormatVersion: 2010-09-09 Description: Rocky Linux 8/9 with NICE DCV (login as rocky) Metadata: License: Description: > Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AWS::CloudFormation::Interface: ParameterGroups: - Label: default: AMI and instance type Parameters: - imageId - instanceType - Label: default: EC2 configuration Parameters: - ec2Name - vpcID - subnetID - assignStaticIP - displayPublicIP - Label: default: Allowed source IP prefixes Parameters: - ingressIPv4 - ingressIPv6 - Label: default: EBS volume configuration Parameters: - volumeSize - deviceName - Label: default: NICE DCV configuration Parameters: - listenPort Parameters: imageId: Type: String Description: AMI ID ( Get ID for your region from https://rockylinux.org/cloud-images/ ). Select appropriate Graviton instanceType (e.g. tg4.medium ) if using arm64 ConstraintDescription: Do specify a valid Image ID AllowedPattern: "ami.+" instanceType: Type: String Description: Instance Type (x86_64 or arm64) ( https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html ) Default: t3.medium ec2Name: Type: String Description: EC2 instance name Default: Rocky Linux 8/9-NICE-DCV vpcID: Type: AWS::EC2::VPC::Id Description: VPC with internet connectivity ConstraintDescription: Do specify a valid value AllowedPattern: ".+" subnetID: Type: AWS::EC2::Subnet::Id Description: Subnet with internet connectivity ConstraintDescription: Do specify a valid value AllowedPattern: ".+" assignStaticIP: Type: String Description: Associate static public IPv4 address ( https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html ) AllowedValues: - "Yes" - "No" Default: "No" displayPublicIP: Type: String Description: Display EC2 public IP in CloudFormation Outputs (select No if EC2 has no public IP) AllowedValues: - "Yes" - "No" Default: "Yes" ingressIPv4: Type: String Description: Allowed source prefix (IPv4) ( e.g. 1.2.3.4/32, get your source IP from https://checkip.amazonaws.com ) Default: 0.0.0.0/0 ingressIPv6: Type: String Description: Allowed source prefix (IPv6) Default: ::/0 listenPort: Type: Number Description: NICE DCV server TCP/UDP port ConstraintDescription: Specify a value between 1024 and 6553 MinValue: 1024 MaxValue: 65535 Default: 8443 volumeSize: Type: Number Description: Volume Size in GiBs (must be equal or larger than snapshot size) Default: 10 deviceName: Type: String Description: Device Name Default: /dev/sda1 Conditions: useElasticIP: !Equals [!Ref assignStaticIP, "Yes"] displayPublicIP: !Equals [!Ref displayPublicIP, "Yes"] Resources: securityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow inbound DCV VpcId: !Ref vpcID SecurityGroupIngress: - Description: NICE DCV (IPv4) IpProtocol: "tcp" FromPort: !Ref listenPort ToPort: !Ref listenPort CidrIp: !Ref ingressIPv4 - Description: NICE DCV QUIC (IPv4) IpProtocol: "udp" FromPort: !Ref listenPort ToPort: !Ref listenPort CidrIp: !Ref ingressIPv4 - Description: NICE DCV (IPv6) IpProtocol: "tcp" FromPort: !Ref listenPort ToPort: !Ref listenPort CidrIpv6: !Ref ingressIPv6 - Description: NICE DCV QUIC (IPv6) IpProtocol: "udp" FromPort: !Ref listenPort ToPort: !Ref listenPort CidrIpv6: !Ref ingressIPv6 SecurityGroupEgress: - Description: Allow all outbound traffic (IPv4) IpProtocol: "-1" CidrIp: 0.0.0.0/0 - Description: Allow all outbound traffic (IPv6) IpProtocol: "-1" CidrIpv6: "::/0" Tags: - Key: StackName Value: !Sub ${AWS::StackName} - Key: StackId Value: !Sub ${AWS::StackId} - Key: Name Value: !Sub "[${AWS::StackName}] - ${ec2Name}" - Key: GitHub Value: https://github.com/aws-samples/amazon-ec2-nice-dcv-samples instanceIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: [ec2.amazonaws.com] Action: ["sts:AssumeRole"] Path: / Policies: # https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-license.html - PolicyName: dcvLicensing PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:GetObject Resource: !Sub "arn:${AWS::Partition}:s3:::dcv-license.${AWS::Region}/*" - PolicyName: gpuDrivers PolicyDocument: # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-amd-driver.html Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:Get* - s3:List* Resource: - !Sub "arn:${AWS::Partition}:s3:::nvidia-gaming" - !Sub "arn:${AWS::Partition}:s3:::nvidia-gaming/*" - !Sub "arn:${AWS::Partition}:s3:::ec2-linux-nvidia-drivers" - !Sub "arn:${AWS::Partition}:s3:::ec2-linux-nvidia-drivers/*" - !Sub "arn:${AWS::Partition}:s3:::ec2-amd-linux-drivers" - !Sub "arn:${AWS::Partition}:s3:::ec2-amd-linux-drivers/*" ManagedPolicyArns: - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore" Tags: - Key: StackName Value: !Sub ${AWS::StackName} - Key: StackId Value: !Sub ${AWS::StackId} - Key: GitHub Value: https://github.com/aws-samples/amazon-ec2-nice-dcv-samples instanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref instanceIamRole ec2Instance: Type: AWS::EC2::Instance CreationPolicy: ResourceSignal: Timeout: PT90M Properties: ImageId: !Ref imageId InstanceType: !Ref instanceType IamInstanceProfile: !Ref instanceProfile SubnetId: !Ref subnetID Monitoring: true SecurityGroupIds: - !Ref securityGroup BlockDeviceMappings: - DeviceName: !Ref deviceName Ebs: VolumeType: gp3 VolumeSize: !Ref volumeSize DeleteOnTermination: true UserData: Fn::Base64: !Sub | #!/bin/bash cd /root/ dnf install -q -y python3 python3-setuptools wget tmux unzip tar curl sed # https://docs.aws.amazon.com/systems-manager/latest/userguide/agent-install-rhel.html if (uname -a | grep x86 1>/dev/null); then dnf install -q -y https://s3.${AWS::Region}.amazonaws.com/amazon-ssm-${AWS::Region}/latest/linux_amd64/amazon-ssm-agent.rpm else dnf install -q -y https://s3.${AWS::Region}.amazonaws.com/amazon-ssm-${AWS::Region}/latest/linux_arm64/amazon-ssm-agent.rpm fi systemctl enable --now amazon-ssm-agent # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-helper-scripts-reference.html # https://aws.amazon.com/premiumsupport/knowledge-center/install-cloudformation-scripts/ wget -4 -nv https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz tar -xvf aws-cfn-bootstrap-py3-latest.tar.gz cd aws-cfn-bootstrap-2.0 python3 setup.py build python3 setup.py install python3 /usr/local/bin/cfn-init -v --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} # https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-prereq.html yum groupinstall -q -y 'Server with GUI' systemctl isolate multi-user.target sed -i "s/^#WaylandEnable=false/WaylandEnable=false/g" /etc/gdm/custom.conf yum install -q -y pulseaudio pulseaudio-utils # https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-server.html rpm --import https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY if ((uname -a | grep x86 1>/dev/null) && (cat /etc/os-release | grep 8. 1>/dev/null)); then wget -nv https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el8-x86_64.tgz tar -xvzf nice-dcv-el8-x86_64.tgz && cd nice-dcv-*-el8-x86_64 elif ((uname -a | grep aarch64 1>/dev/null) && (cat /etc/os-release | grep 8. 1>/dev/null)); then wget -nv https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el8-aarch64.tgz tar -xvzf nice-dcv-el8-aarch64.tgz && cd nice-dcv-*-el8-aarch64 else wget -nv https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el9-x86_64.tgz tar -xvzf nice-dcv-el9-x86_64.tgz && cd nice-dcv-*-el9-x86_64 fi yum install -y ./nice-dcv-server-*.rpm yum install -y ./nice-dcv-web-viewer-*.rpm yum install -y ./nice-xdcv-*.rpm # https://docs.aws.amazon.com/dcv/latest/adminguide/enable-quic.html cp /etc/dcv/dcv.conf /etc/dcv/dcv.conf.org sed -i "s/^#enable-quic-frontend=true/enable-quic-frontend=true/g" /etc/dcv/dcv.conf # session storage: https://docs.aws.amazon.com/dcv/latest/userguide/using-transfer.html # https://docs.aws.amazon.com/dcv/latest/adminguide/managing-sessions-start.html#managing-sessions-start-manual cat << EoF > /etc/systemd/system/dcv-virtual-session.service [Unit] Description=Create DCV virtual session After=default.target network.target [Service] ExecStart=/opt/dcv-virtual-session.sh [Install] WantedBy=default.target EoF cat << EoF > /opt/dcv-virtual-session.sh #!/bin/bash dcvUser=rocky while true; do if (/usr/bin/dcv list-sessions | grep \$dcvUser 1>/dev/null); then sleep 5 else /usr/bin/dcv create-session \$dcvUser --owner \$dcvUser --storage-root /home/\$dcvUser /usr/bin/dcv list-sessions fi done EoF chmod +x /opt/dcv-virtual-session.sh # https://docs.aws.amazon.com/dcv/latest/adminguide/manage-port-addr.html sed -i "/^web-port=/d" /etc/dcv/dcv.conf sed -i "/^quic-port=/d" /etc/dcv/dcv.conf sed -i "/^\[connectivity\]/a web-port=${listenPort}" /etc/dcv/dcv.conf sed -i "/^\[connectivity\]/a quic-port=${listenPort}" /etc/dcv/dcv.conf cd /root/ # https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html yum remove awscli -y if (uname -a | grep x86 1>/dev/null); then curl -s https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip else curl -s https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip -o awscliv2.zip fi unzip -q -o awscliv2.zip ./aws/install -b /usr/bin echo "export AWS_CLI_AUTO_PROMPT=on-partial" >> /home/rocky/.bashrc # yum-cron yum install -q -y dnf-automatic sed -i 's/apply_updates = no/apply_updates = yes/g' /etc/dnf/automatic.conf systemctl enable --now dnf-automatic.timer # DCV update script cat << EoF > /home/rocky/update-dcv #!/bin/bash cd /tmp if ((uname -a | grep x86 1>/dev/null) && (cat /etc/os-release | grep 8. 1>/dev/null)); then rm -f nice-dcv-el8-x86_64.tgz wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el8-x86_64.tgz tar -xvzf nice-dcv-el8-x86_64.tgz && cd nice-dcv-*-el8-x86_64 elif ((uname -a | grep aarch64 1>/dev/null) && (cat /etc/os-release | grep 8. 1>/dev/null)); then rm -f nice-dcv-el8-aarch64.tgz wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el8-aarch64.tgz tar -xvzf nice-dcv-el8-aarch64.tgz && cd nice-dcv-*-el8-aarch64 else rm -f nice-dcv-el9-x86_64.tgz wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-el9-x86_64.tgz tar -xvzf nice-dcv-el9-x86_64.tgz && cd nice-dcv-*-el9-x86_64 fi sudo dcv close-session rocky sudo systemctl stop dcvserver dcv-virtual-session sudo yum install -y ./nice-dcv-server-*.rpm sudo yum install -y ./nice-dcv-web-viewer-*.rpm sudo yum install -y ./nice-xdcv-*.rpm sudo sed -i "s/^#enable-quic-frontend=true/enable-quic-frontend=true/g" /etc/dcv/dcv.conf sudo systemctl restart dcvserver dcv-virtual-session EoF chmod +x /home/rocky/update-dcv chown rocky:rocky /home/rocky/update-dcv # AWS CLI update script cat << EoF > /home/rocky/update-awscli #!/bin/bash cd /tmp rm -f awscliv2.zip if (uname -a | grep x86 1>/dev/null) then curl https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip else curl https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip -o awscliv2.zip fi unzip -q -o awscliv2.zip sudo ./aws/install --update -b /usr/bin EoF chmod +x /home/rocky/update-awscli chown rocky:rocky /home/rocky/update-awscli # GPU drivers download script cat << EoF > /home/rocky/download-NVIDIA-GRID-driver #!/bin/bash clear echo echo NOTICE: These downloads are for GPU instances and are available to AWS customers only echo echo By downloading, you agree to conditions and are bound by license terms as stated on echo https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html echo sudo mkdir -p /home/rocky/Downloads/Drivers sudo chown rocky:users /home/rocky/Downloads/Drivers cd /home/rocky/Downloads/Drivers pwd aws s3 cp --recursive s3://ec2-linux-nvidia-drivers/latest/ . chmod +x /home/rocky/Downloads/Drivers/*.run EoF cat << EoF > /home/rocky/download-NVIDIA-Gaming-driver #!/bin/bash clear echo echo NOTICE: These downloads are for GPU instances and are available to AWS customers only echo echo By downloading, you agree to conditions and are bound by license terms as stated on echo https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html echo sudo mkdir -p /home/rocky/Downloads/Drivers sudo chown rocky:users /home/rocky/Downloads/Drivers cd /home/rocky/Downloads/Drivers pwd aws s3 cp --recursive s3://nvidia-gaming/linux/latest/ . sudo mkdir -p /etc/nvidia sudo curl -s -o /etc/nvidia/GridSwCert.txt "https://nvidia-gaming.s3.amazonaws.com/GridSwCert-Archive/GridSwCertLinux_2021_10_2.cert" EoF cat << EoF > /home/rocky/download-AMD-driver #!/bin/bash clear echo echo NOTICE: These downloads are for GPU instances and are available to AWS customers only echo echo By downloading, you agree to conditions and are bound by license terms as stated on echo https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-amd-driver.html echo sudo mkdir -p /home/rocky/Downloads/Drivers sudo chown rocky:users /home/rocky/Downloads/Drivers cd /home/rocky/Downloads/Drivers pwd aws s3 cp --recursive s3://ec2-amd-linux-drivers/latest/ . cat << EOF > /home/ec2-user/Downloads/Drivers/amd-xorg.conf Section "ServerLayout" Identifier "Layout0" Screen 0 "Screen0" InputDevice "Keyboard0" "CoreKeyboard" InputDevice "Mouse0" "CorePointer" EndSection Section "Files" ModulePath "/opt/amdgpu/lib64/xorg/modules/drivers" ModulePath "/opt/amdgpu/lib/xorg/modules" ModulePath "/opt/amdgpu-pro/lib/xorg/modules/extensions" ModulePath "/opt/amdgpu-pro/lib64/xorg/modules/extensions" ModulePath "/usr/lib64/xorg/modules" ModulePath "/usr/lib/xorg/modules" EndSection Section "InputDevice" # generated from default Identifier "Mouse0" Driver "mouse" Option "Protocol" "auto" Option "Device" "/dev/psaux" Option "Emulate3Buttons" "no" Option "ZAxisMapping" "4 5" EndSection Section "InputDevice" # generated from default Identifier "Keyboard0" Driver "kbd" EndSection Section "Monitor" Identifier "Monitor0" VendorName "Unknown" ModelName "Unknown" EndSection Section "Device" Identifier "Device0" Driver "amdgpu" VendorName "AMD" BoardName "Radeon MxGPU V520" BusID "PCI:0:30:0" EndSection Section "Extensions" Option "DPMS" "Disable" EndSection Section "Screen" Identifier "Screen0" Device "Device0" Monitor "Monitor0" DefaultDepth 24 Option "AllowEmptyInitialConfiguration" "True" SubSection "Display" Virtual 3840 2160 Depth 32 EndSubSection EndSection EOF EoF chmod +x /home/rocky/download-*-driver chown rocky:rocky /home/rocky/download-*-driver # Add NICE DCV ports (https://firewalld.org/documentation/man-pages/firewall-offline-cmd.html) # Get around ":dbus.proxies:Introspect error on :1.170:/org/fedoraproject/FirewallD" error systemctl stop firewalld firewall-offline-cmd --add-port ${listenPort}/tcp firewall-offline-cmd --add-port ${listenPort}/udp systemctl disable firewalld # text console: DCV virtual sessions only systemctl isolate multi-user.target systemctl set-default multi-user.target systemctl daemon-reload systemctl enable --now dcvserver dcv-virtual-session.service # cfn-init completed so signal success or not python3 /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} Tags: - Key: Name Value: !Ref ec2Name - Key: StackName Value: !Sub ${AWS::StackName} - Key: StackId Value: !Sub ${AWS::StackId} - Key: GitHub Value: https://github.com/aws-samples/amazon-ec2-nice-dcv-samples elasticIP: Condition: useElasticIP Type: AWS::EC2::EIP Properties: Domain: vpc InstanceId: !Ref ec2Instance Tags: - Key: StackName Value: !Sub ${AWS::StackName} - Key: StackId Value: !Sub ${AWS::StackId} - Key: Name Value: !Sub "[${AWS::StackName}] - ${ec2Name}" - Key: GitHub Value: https://github.com/aws-samples/amazon-ec2-nice-dcv-samples Outputs: EC2Instance: Description: EC2 instance console Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#Instances:search=${ec2Instance}" SSMsessionManager: Description: SSM Session Manager login ("sudo passwd rocky" to change password) Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/systems-manager/session-manager/${ec2Instance}" DCVwebConsole: Description: DCV web console (login as rocky) Value: !Sub - "https://${IpAddress}:${listenPort}" - IpAddress: !If [ displayPublicIP, !GetAtt ec2Instance.PublicIp, !GetAtt ec2Instance.PrivateIp, ]