A username is lowercase letters, digits, - or _, starts with a letter, and is at most 32 characters.
The commands below use these values. The key is optional, because Tailscale logins don't need one.
-
Check that you can reach the machines
You need to be on the AI Foundry tailnet, and root logins have to work for you. The first time, Tailscale prints a URL: open it to confirm it's you.
ssh root@aifoundry1 hostnameIt should print
aifoundry1. -
Save the script
Paste this into a terminal on your own computer. It writes
add-lab-user.shto the current folder and makes it executable.cat > add-lab-user.sh <<'ADD_LAB_USER' #!/usr/bin/env bash # Create an account for someone on every AI Foundry lab machine (aifoundry1, aifoundry2, ...). # Usage: add-lab-user.sh <username> [<ssh-pubkey-file>] ["Full Name"] # Needs `ssh root@aifoundryN` to work from this machine. Safe to re-run: an existing account is # left alone and the key is only added if it is missing. set -euo pipefail usage() { echo "usage: $0 <username> [<ssh-pubkey-file>] [\"Full Name\"]" >&2; exit 1; } user=${1:-} keyfile=${2:-} name=${3:-} [[ $user =~ ^[a-z][a-z0-9_-]{0,31}$ ]] || usage key= if [[ -n $keyfile ]]; then key=$(head -n1 "$keyfile") if [[ ! $key =~ ^(ssh-|ecdsa-|sk-) ]] || ! ssh-keygen -lf - <<<"$key" >/dev/null 2>&1; then echo "$keyfile is not an SSH public key (pass the .pub file)" >&2; exit 1 fi fi # Every aifoundryN on the tailnet, unless HOSTS="aifoundry1 aifoundry2" is set. hosts=${HOSTS:-$(tailscale status 2>/dev/null | awk '$2 ~ /^aifoundry[0-9]+$/ {print $2}' | sort)} hosts=${hosts:-aifoundry1 aifoundry2 aifoundry3} rc=0 for h in $hosts; do echo "== $h" { printf 'user=%q key=%q name=%q\n' "$user" "$key" "$name" cat <<'EOF' set -euo pipefail if id "$user" >/dev/null 2>&1; then echo "account exists: $(id "$user")" else adduser --quiet --disabled-password --comment "$name" --shell /bin/bash "$user" </dev/null echo "created: $(id "$user")" fi if [ -n "$key" ]; then # Write as the user, so a symlink in their home cannot redirect a root write. runuser -u "$user" -- sh -c 'umask 077; mkdir -p "$1/.ssh"; f="$1/.ssh/authorized_keys"; touch "$f" if grep -qxF "$2" "$f"; then echo "key already present"; else printf "%s\n" "$2" >>"$f"; echo "key added"; fi' \ sh "$(getent passwd "$user" | cut -d: -f6)" "$key" fi for d in /dev/et*; do [ -e "$d" ] || continue if runuser -u "$user" -- test -r "$d" -a -w "$d"; then echo "$d: read/write ok"; else echo "$d: NO ACCESS"; fi done EOF } | ssh -o BatchMode=yes -o ConnectTimeout=15 "root@$h" bash -s || { echo "!! $h: failed" >&2; rc=1; } done exit $rc ADD_LAB_USER chmod +x add-lab-user.sh -
Run it
Save their public key next to the script as
alice.pub. On their computer it is usually~/.ssh/id_ed25519.pub../add-lab-user.sh alice alice.pub 'Alice Example'It finds every
aifoundryNmachine on the tailnet, creates the account where it is missing, adds the key, and checks that the account can read and write each/dev/et*device. Running it again is safe. To do only some machines, putHOSTS="aifoundry2"in front of the command.== aifoundry1 created: uid=1010(alice) gid=1010(alice) groups=1010(alice),100(users) key added /dev/et0_mgmt: read/write ok /dev/et0_ops: read/write ok /dev/et1_mgmt: read/write ok /dev/et1_ops: read/write ok == aifoundry2 created: uid=1020(alice) gid=1020(alice) groups=1020(alice),100(users) key added /dev/et0_mgmt: read/write ok /dev/et0_ops: read/write ok == aifoundry3 created: uid=1005(alice) gid=1005(alice) groups=1005(alice),100(users) key added /dev/et0_mgmt: read/write ok /dev/et0_ops: read/write ok -
Send them the login steps
If they are not on the tailnet yet, a tailnet admin has to invite them first (Tailscale admin console, Users, Invite users). If the account is yours, these are your next steps.
Your account on the AI Foundry lab machines is ready: alice on aifoundry1, aifoundry2 and aifoundry3. 1. If you are not on the AI Foundry tailnet yet, accept the Tailscale invite and install Tailscale. 2. Log in with: ssh alice@aifoundry1 If Tailscale prints a URL, open it to confirm it's you. It asks again every so often. 3. The ET tools are in /opt/et/bin. To put them on your PATH, run this once on each machine: echo 'export PATH=/opt/et/bin:$PATH' >> ~/.bashrc
The machines
| Machine | Devices | Device nodes |
|---|---|---|
aifoundry1 | 2 | /dev/et0_mgmt /dev/et0_ops /dev/et1_mgmt /dev/et1_ops |
aifoundry2 | 1 | /dev/et0_mgmt /dev/et0_ops |
aifoundry3 | 1 | /dev/et0_mgmt /dev/et0_ops |
All three run Ubuntu 24.04. The device nodes are crw-rw-rw- (mode 0666), so any account can use the cards without joining a group. The ET tools are in /opt/et/bin. Each machine has its own /home, so files don't carry over between machines.
How logins work
Logins go through Tailscale SSH. The tailnet's SSH rules decide who may connect, so a machine only needs the account to exist, and there are no passwords to hand out. The SSH key is a fallback for plain sshd on the lab's local network.
Giving someone sudo
New accounts have no password, so they can't use sudo. To give someone sudo on one machine, add them to the sudo group and set a starting password, which they can change later with passwd:
ssh -t root@aifoundry1 'usermod -aG sudo alice && passwd alice'Without the script
Run these for each machine. adduser asks for the full name, and the other questions can stay empty. The second command is only needed if they sent a key.
ssh -t root@aifoundry2 adduser --disabled-password alice
ssh alice@aifoundry2 'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys' < alice.pub
If something fails
!! aifoundry2: failed- Run
ssh root@aifoundry2 hostnameto see why. Usually Tailscale wants you to confirm in the browser, or the machine is offline. /dev/et0_ops: NO ACCESS- The account can't read and write that device. Check
ls -l /dev/et*on that machine: the nodes should becrw-rw-rw-. - Tailscale refuses their login
- They are on the tailnet, but its SSH rules don't cover them yet. A tailnet admin needs to add them to the SSH rules in the tailnet policy file.