Install Rust Toolchain management with cloud-init on an Linux Azure VM

Install Rust Toolchain management with cloud-init on an Linux Azure VM

Get the Rust toolchain installed with an automated process working for a non-root user.

Motivation

For one of my current projects I do remote SSH development from Visual Studio Code on Windows into a Linux Azure VM.

build structure and performance requirements prevent me from using local WSL or GitHub Codespaces

To get a reproducible environment for such a case I usually turn to cloud-init support for virtual machines in Azure - using VM imaging or other tools like Ansible still would be too much for this simple requirement.

However when making an installation of rustup / Rust Toolchain management during an automated process like Docker or as in my case with cloud-init, that installation is usually executed in root context and the target user may not have access or be able to use rustup.

Being aware that this is an edge case, which may not be reusable directly by you out there, I still wanted to share some aspects which are not easy to find out there - neither for multi-user rustup installation nor for user context handling within cloud-init.

as cloud-init is primarily used to bring up plain server systems in the cloud, multi user considerations surely are not that relevant

cloud-init.txt

This cloud-init.txt is tailored for an Ubuntu 22.04 based VM. It basically installs rustup toolchain and then makes it available for the target user I later use to SSH into the system:

#cloud-config
package_upgrade: true
packages:
- apt-transport-https
- build-essential
- cmake
runcmd:
- export USER=$(awk -v uid=1000 -F":" '{ if($3==uid){print $1} }' /etc/passwd)
- export RUSTUP_HOME=/opt/rust
- export CARGO_HOME=/opt/rust
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y --no-modify-path --default-toolchain stable --profile default
- echo '\n\n# added by cloud init\nsource /opt/rust/env' >> /home/$USER/.profile
- sudo -H -u $USER bash -c 'source /opt/rust/env && rustup default stable'

Let's break down the essential parts:

determine the VM's administration user during cloud-init

The user name, which is put on the Azure VM, is controlled by a configuration like this e.g. in Bicep

    osProfile: {
      computerName: computerName
      adminUsername: adminUsername
      adminPassword: adminPasswordOrKey
      customData: base64(customData)
      linuxConfiguration: ((authenticationType == 'password') ? json('null') : linuxConfiguration)
    }

but is not available during cloud-init. So with this line below, the username of this user is obtained and put into an environment variable for later use:

- export USER=$(awk -v uid=1000 -F":" '{ if($3==uid){print $1} }' /etc/passwd)

it is assumed that the first user created on the Azure VM will get uid 1000

install rustup toolchain

With these statements the toolchain is installed into a folder that can be accessed by the user later:

- export RUSTUP_HOME=/opt/rust
- export CARGO_HOME=/opt/rust
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y --no-modify-path --default-toolchain stable --profile default

--default-toolchain stable --profile default - although not directly required during root user installation, it helps controlling the unattended installation and avoiding warnings

add toolchain PATH for target user

To put the toolchain into PATH the user's .profile is extended:

- echo '\n\n# added by cloud init\nsource /opt/rust/env' >> /home/$USER/.profile

$USER is determined above

install toolchain for target user

Section above will make toolchain available for the user in general, this line installs a default profile for the user:

- sudo -H -u $USER bash -c 'source /opt/rust/env && rustup default stable'
  • sudo -H -u $USER executes the commands specified with bash -c in the context of the target user
  • source /opt/rust/env is required because addition to PATH from above is not yet applied at this time

But wait, there's more...

  • $USER environment variable also can be used e.g. when installing Docker on the VM and making the user member of the docker group
  • how to add latest release of docker-buildx for multi-platform builds to the VM automatically
#cloud-config
package_upgrade: true
packages:
- apt-transport-https
- jq
- build-essential
- cmake
- libssl-dev
- openssl
- unzip
- pkg-config
runcmd:
- export USER=$(awk -v uid=1000 -F":" '{ if($3==uid){print $1} }' /etc/passwd)

- export RUSTUP_HOME=/opt/rust
- export CARGO_HOME=/opt/rust
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y --no-modify-path --default-toolchain stable --profile default
- echo '\n\n# added by cloud init\nsource /opt/rust/env' >> /home/$USER/.profile
- sudo -H -u $USER bash -c 'source /opt/rust/env && rustup default stable'

- curl -fsSL https://get.docker.com -o get-docker.sh
- sudo sh get-docker.sh
- sudo usermod -aG docker $USER
- wget -q -O /usr/libexec/docker/cli-plugins/docker-buildx $(curl -s https://api.github.com/repos/docker/buildx/releases/latest | jq -r ".assets[] | select(.name | test(\"linux-amd64\")) | .browser_download_url")
- chmod u+x /usr/libexec/docker/cli-plugins/docker-buildx

- curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

Please let me know whether singular nuggets like this make sense posting about in this community.

Did you find this article valuable?

Support The ancient IT guy blog by becoming a sponsor. Any amount is appreciated!