Install Rust Toolchain management with cloud-init on an Linux Azure VM
Tags: azure, vm, rust
Published: 2022-08-29 05:39:47

---
title: Install Rust Toolchain management with cloud-init on an Linux Azure VM
published: true
description: Get the Rust toolchain installed with an automated process working for a non-root user.
tags: #azure #vm #rust
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ls2bitrg00o3ed3mxgmn.JPG
# Use a ratio of 100:42 for best results.
---

## 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](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init#deploying-a-cloud-init-enabled-virtual-machine) - 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**](https://www.rust-lang.org/tools/install) during an automated process like **Docker** or as in my case with [**cloud-init**](https://cloudinit.readthedocs.io/en/latest/index.html), 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](https://github.com/rust-lang/rustup/issues/1085) 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:

### <a id="user" />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](#user)

### 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.