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 withbash -c
in the context of the target usersource /opt/rust/env
is required because addition toPATH
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 thedocker
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.