RPI_hpc v1.0

The workflow here aims for a pre-configured setup and minimal package dependency so, in the upcoming iterations these can be automated via scripts. There are few sections where certain commands doesn't work but are included for completeness.
Flashing arm image
Raspbian lite OS is used across all the nodes. To flash the OS, download the aarch64 image and identify the SD card using lsblk command.
$ wget https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2025-12-04/2025-12-04-raspios-trixie-arm64-lite.img.xz
$ lsblk
Umount all SD card partition, xzcat command will decompresses the file, now flash using dd command (disk copying tool).
# umount /dev/\*blk0p1
# xzcat 2025-12-04-raspios-trixie-arm64-lite.img.xz | dd of=/dev/mmcblk0 bs=1M oflag=direct status=progress
# 2986344448 bytes (3.0 GB, 2.8 GiB) copied, 283.074 s, 10.5 MB/s
Configuring OS Pre-boot
There are two ways one can setup the service before booting, raspbian OS respect files on /boot partition so to
setup user and sshd server, source
# cd /boot
# touch ssh
# echo "password" | openssl passwd -6 -stdin > userconf.txt
Edit userconf.txt to append the username infront as rpi4:<encrpyted_text>. Functionality with this preboot config
file is limited so, chroot is also needed. So after this, the first boot, this will automatically detect 'ssh' and creats a user rpi4.
Chroot into aarch64 sd card
Since I'm working on amd64 system and trying to chroot into arm64/aarch64 image, I need to setup the emulator using qemu-static packages, (forum discussion about this topic). Install the appropriate qemu package and mount the / and /boot partition of the sd card.
# pacman -S endeavouros/qemu-arm-aarch64-static-bin
# mount /dev/mmcblk0p2 /tmp/mnt
# mount /dev/mmcblk0p1 /tmp/mnt/boot
To enable essential services while using chroot, /proc and /sys partitions also needs to be mounted,
source
cd /tmp/mnt
sudo mount --bind /dev dev/
sudo mount --bind /sys sys/
sudo mount --bind /proc proc/
sudo mount --bind /dev/pts dev/pts
sudo chroot .
Firstly, setup locale, by uncommenting en_GB.UTF-8 in /etc/locale.gen and 'LANG=GB.UTF-8' in /etc/default/locale.
Source these two files and check with locale.
Initial Network setup
Raspbian by defaults has all the conflicting network package installed together! it has dhcpcd, systemd-networkd, NetworkManager, dhclient and what not. For rest of the guide, we purge all configs of network packages except the NetworkManager, this is essentially to make system clean without conflicting configs.
NetworkManager by default has dhcp functionality to get dynamic ip when connected to network interfaces but from my brief testing there is bug in this functionality. On the contrary dhcpcd and dhclient are fully fledged dhcp server which we don't require so it's pointless to have configuration set for these and make sure these services are disabled.
# systemctl disable dhcpcd
# systemctl disable dhclient
# systemctl disable systemd-networkd
# systemctl enable NetworkManager
Setting up static ip - preboot (DOESN'T WORK)
To setup up static ip on Debian based system, the creation of following file /etc/network/interfaces was recommended, but the following doesn't work in Raspbian rather breaks up the initial creation of ethernet eth0 device.
# cat /etc/network/interfaces
iface eth0 inet static
address 10.61.135.4
netmask 255.255.255.0
gateway 10.61.135.0
Configuring Static pre boot will be rectified in the upcoming iterations. Reference of other possible setups and EEPROM bootloader setup from RPI 4.
The configuration files for NetworkManager are located /etc/NetworkManager/system-connections/, but these cannot be created prior to initial boot because the MAC address of the SBC needs embedded into these \*connection files. The way as of now is copying the configs after the first boot and creation of wired and wireless connection using nmcli or nmtui.
Note: Before exiting chroot, make sure dhcpcd, systemd-networkd are disabled and sshd-config is setup properly.
Tcp Forwarding is enabled in /etc/sshd/sshd.conf.
#AllowTcpForwarding yes
Unmount the SD card root partition /.
umount -l /tmp/mnt/
Setting up static ip - afterboot
Since only NetworkManager service is enabled with chroot. It becomes straightforward to configure once SD card has boot on RPI. On the first boot, NM auto configures the ethernet device as 'Wired connection 1'.
This is reflected in the following directory,
root@rpi2:/etc/NetworkManager/system-connections# ls
'Wired connection 1.nmconnection'
The initial [connection] has the device specific uuid. When I tried to manually
generate with uuid(1), NM couldn't recognize the network connections, due to this
restriction leave the [connection] untouched. Replace the rest according to the
node number (X), eg.
[ethernet]
[ipv4]
address1=10.61.135.X/24
gateway=10.61.135.0
may-fail=false
method=manual
[ipv6]
addr-gen-mode=default
method=auto
To check whether the configuration is valid, nmtui can be used. These should
be reflected as shown in the screen capture below.

This connection can be activated instantly in the nmtui interface or a reboot
take care of setting the configured static ip.
Login node setup
Setting up eduroam wifi - afterboot
As mentioned in the project description, the cluster will exposed to cyberspace replicating real world HPC services. This requires atleast one of the login nodes to have working network connections. To make the setup portable, cluster is hosted on the university wireless network. Make sure the wifi is enabled on the rpi,
$ nmcli connection
$ nmcli radio
$ # remove kill all switch using any of the below
$ rfkill unblock all
$ ifconfig wlan0 up
$ nmcli radio wifi on
University network is based on eduroam. According to the claim, once the network is configured, it should be usable across all the institutions that has eduroam.
Setting up the connection initially was one of the painful things, since the offical installer required GUI to work!
I'm already connected to eduroam through a *nix machine and existing system-connections file helped to dissect the right network options. Before modifying on rpi nodes, an initial wifi configurations has to be created, additional info to configure from scratch
root@rpi2:/$ nmcli connection add type wifi con-name "eduroam" \
ifname wlan0 ssid "eduroam"
root@rpi2:/etc/NetworkManager/system-connections$ ls
eduroam.nmconnection 'Wired connection 1.nmconnection'
This has generated eduroam.nmconnection files. Leaving out the [connection] block replace the rest with the working nmconnection file. A sample is given below,
root@rpi2:/etc/NetworkManager/system-connections# cat eduroam.nmconnection
# --- auto generated from nmcli/nmtui
[connection]
id=eduroam
uuid=---specific-to-device----
type=wifi
interface-name=wlan0
timestamp=1764865738
# --- blocks that needs to be replaced
[wifi]
mode=infrastructure
ssid=eduroam
[wifi-security]
key-mgmt=wpa-eap
[802-1x]
eap=peap;
identity=c500@add.ac.com
password=xxxxxxxxx
phase2-auth=mschapv2
[ipv4]
method=auto
route-metric=1
[ipv6]
addr-gen-mode=default
method=auto
These configurations can be verified by checking the connection fields in nmtui.
Now the connection can be started with,
$ nmcli connnection # list of name
$ nmcli connection up eduroam # up <name>
It's likely ping-ing the cyberworld doesn't work right away at this point. There are two things that needs to be taken care,
Dynamic ip for wlan0 with NM
Although NetworkManager has inbuilt dhcp server, in my case it always struggled
to assign a dynamic ip for the wlan0 device. Since dhcpcd is baked into this raspbian image, NM can be instructed to use it as a helper. An important note here is, dhcpcd shouldn't be started as a standalone service rather invoked externally by including the
config file /etc/NetworkManager/conf.d/dhcp-client.confi.
[main]
dhcp=dhcpcd
Restart the connection,
nmcli connection down <name>
nmcli connection up <name>
Wifi priority - mtu values
After a reboot, check the routing table.
Make sure wifi device's mtu value is the least for wifi device so it can ping cyberspace
overriding the ethernet, this is set in /etc/NetworkManager/system-connections/\* file by editing route-metric.
root@raspberrypi:/etc/NetworkManager/conf.d# ip route
default via 10.xx.x28.x dev wlan0 proto dhcp src 1x.61.xx3.2xx metric 1
default via 10.61.135.0 dev eth0 proto static metric 100
10.6x.1xx.0/18 dev wlan0 proto kernel scope link src 10.x1.xx3.xx1 metric 1
10.61.135.0/24 dev eth0 proto kernel scope link src 10.61.135.2 metric 100
With the setup so far, login nodes should access the web and compute node should be internally reachable.
Mirroring network configs
From my brief testing I found when powersaving mode is enabled in rpi, sshd server goes down in random intervals. It's best to configure two nodes with access to the cyberspace, rather than one.
# copy wifi config from the login node
$ scp /etc/NetworkManager/system-connections/\*eduroam* rpiX:/tmp/
Ssh into other rpi node,
$ nmcli radio wifi on
$ nmcli connection add type wifi ifname wlan0 ssid <ssid>
Now copy the config from initial login node starting from [wifi] block and retain [connection] block from the current node in /etc/NetworkManager/system-connections/.
Set the config file permissions and restart the connection.
$ # cp the nmconnection eduroam file and set opt permission
$ sudo chmod 600 eduroam_final.nmconnection
$ nmcli con up eduroam
Internal connections
Check the internal status of interconnected nodes,
rpi2@raspberrypi:~ $ alias listrpi
alias listrpi='nmap -sn 10.61.135.0/24'
rpi2@raspberrypi:~ $ listrpi
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-12 03:27 GMT
Nmap scan report for mist-10-61-1 (10.61.135.2)
Host is up (0.0015s latency).
Nmap scan report for mist-10-61-135-3 (10.61.135.3)
Host is up (0.0012s latency).
Nmap scan report for mist-10-61-135-4 (10.61.135.4)
Host is up (0.0011s latency).
Nmap scan report for mist-10-61-135-5 (10.61.135.5)
Host is up (0.00097s latency).
Nmap scan report for mist-10-61-135-6 (10.61.135.6)
Host is up (0.00087s latency).
Nmap scan report for mist-10-61-135-7 (10.61.135.7)
Host is up (0.00099s latency).
Nmap done: 256 IP addresses (6 hosts up) scanned in 2.86 seconds
or
# Finding new rpi on the local network
$ sudo apt install arp-scan
$ sudo arp-scan –interface=eth0 10.61.135.0/16
Copy ssh-keys across the identified nodes for easier access,
for i in $(seq 3 7); do ssh-copy-id rpi$i@10.61.135.$i; done
Login node setup
As for HPCv1.0 is concerned, the ports of the login nodes are exposed to cybperspace only via the internal wireguard network of OpenBSD server. Only the device which are connected through this network can see and ssh the HPC.
Server side wireguard.
Use the shell script for making client's private & public keys
#!/bin/sh
doas wg genkey | doas tee client${1}-private.key | doas wg pubkey > client${1}-public.key
To create client config, save server's public key and client's private. This file will be sent/given to the client machine.
$ cat client14.conf
# specific to this client's conf
[Interface]
Address = 10.0.0.14/32
PrivateKey = <generated from shell script>
Dns = 10.0.0.1
# server's public info, common across all clientX.conf
[Peer]
PublicKey = KTaX28LTySHtqZrKlA/XzXFVqlkR5/hu28jm0=
Endpoint = x.171.x.105:x
AllowedIPs = 0.0.0.0/0, ::/0
Reload the config for wireguard server wg setconf wg0 /etc/wireguard/server.conf
Wireguard on Login node
To setup the wireguard,
apt install wireguard-tools
scp server:/tmp/clientx.conf /etc/wireguard/wg0
To resolve dns properly, the easiest way that works with wireguard is setting systemd-resolved (frowns)
apt install systemd-resolved
systemctl enable systemd-resolved && systemctl start systemd-resolved
wg-quick up wg0
systemctl enable wg-quick@wg0.server # persist after boot
The network can be checked,
root@raspberrypi:/etc/wireguard# wg
interface: wg0
public key: AuKQhVHEFTnHadajdUk7Q39ncR5qOSH9RQhU=
private key: (hidden)
listening port: 47084
fwmark: 0xca6c
peer: dfruixdsfaldsk/XzXFVqlkR5/hu28jm0=
endpoint: x.171.x.x:x
allowed ips: 0.0.0.0/0, ::/0
latest handshake: 24 seconds ago
transfer: 380 B received, 3.34 KiB sent
Sharing Networking of head node
Check out this link for wifi connection via ethernet port.
On the login node,
sudo nmcli c add con-name custom-shared-con type ethernet ifname eth0 ipv4.method shared ipv6.method ignore
sudo nmcli c up custom-shared-con
On the node side,
$ sudo nmcli con mod "Wired connection 1" ipv4.address "10.61.135.X/24"
$ sudo nmcli con mod "Wired connection 1" ipv4.dns "10.61.135.2"
$ sudo nmcli con mod "Wired connection 1" ipv4.gateway "10.61.135.2"
$ sudo nmcli con mod "Wired connection 1" ipv4.method "manual"
$ sudo nmcli con up "Wired connection 1" # restart
$ cat /etc/resolve.conf # initial check
Inspirations
- https://garrettmills.dev/blog/2018/11/15/Building-a-Raspberry-Pi-Cluster-Part-I/
- https://medium.com/@hghcomphys/building-slurm-hpc-cluster-with-raspberry-pis-step-by-step-guide-ae84a58692d5
- https://carpentriesoffline.org/projects/miniHPC.html
Time sync across nodes
Install ntpdate to command the synchronization of system's date and time with a network.
sudo apt install ntpdate -y
Installation of slurm packages
$apt search slurm
slurm-wlm/stable 24.11.5-4 arm64
Slurm Workload Manager
$ sudo apt install slurmj
Setup munge
Before proceeding to slurm setup, configure munge.
Munge is a dependency for slurm, a helper to interact with nodes.
Interacts with keys generated by time, so times needs to be synced
between rpis!
Checking network time service,
$ sudo timedatectl
If NTP (Network Time Protocol) service is not active, run,
$ sudo timedatectl set-ntp True
Munge on login & compute nodes
This follows from this git guide.
Setting up file permission for munge group, make sure /etc/munge/munge.key is present.
sudo apt install munge libmunge2 libmunge-dev
sudo chown -R munge: /etc/munge/ /var/log/munge/ /var/lib/munge/ /run/munge/
sudo chmod 0700 /etc/munge/ /var/log/munge/ /var/lib/munge/
sudo chmod 0755 /run/munge/
sudo chmod 0700 /etc/munge/munge.key
sudo chown -R munge: /etc/munge/munge.key
sudo systemctl enable munge && sudo systemctl restart munge
Before going to the client side,
cp /etc/munge/munge.key /tmp/
chmod +r /tmp/munge.key
scp this key over all the nodes, and place it on the /etc/munge then,
re-run the same permission commands.
munge -n | ssh rpiX unmunge
STATUS: Success (0)
ENCODE_HOST: raspberrypi (127.0.1.1)
Slurm setup
sudo apt install slurmd slurm-client -y
Copy sample config file at /usr/share/doc/slurm-client/examples/slurm.conf.simple to /etc/slurm. Also refer,
man 5 slurm.conf
Few things to edit in the base config, reference here
SlurmctldHost, as rpi host and address. For this iteration keeping all the default options as it is and just defining the appropriate nodes and finally the 'shortjobs' partition to run jobs.
NodeName=rpi3 NodeAddr=ip CPUs=4 State=UNKNOWN
NodeName=rpi3 NodeAddr=ip CPUs=4 State=UNKNOWN
PartitionName=shortjob Nodes=ALL Default=YES MaxTime=INFINITE State=UP
CPU resource allocations
Copy the example config from /usr/share/doc/slurm-client/examples/cgroup.conf
Add just one parameter for
AllowedDevicesFile="/etc/slurm/cgroup_allowed_devices_file.conf"
Starting slurmd and slurmctld (controller) daemons!
Debugging slurm service
First make sure to change the host names using,
hostnamectl set-hostname new-hostname
After enabling and starting the slurmd service, this will make login node act as one of the compute node.
Read systemctl status slurmd, few inbuilt options
needs to be commented out, in cgroups.conf & slurm.conf
#CgroupAutomount=yes
Also include the login node's address in the slurm config file.
rpi2@rpi2:~ $ systemctl status slurmd
● slurmd.service - Slurm node daemon
Loaded: loaded (/usr/lib/systemd/system/slurmd.service; enabled; preset: enabled)
Active: active (running) since Fri 2026-02-13 05:26:32 GMT; 31s ago
Invocation: 944640baed6a47afaf8afe1413a4cb3d
Docs: man:slurmd(8)
Main PID: 4230 (slurmd)
If the node deamon has succesfully, time to start the controller daemon with
sudo systemctl enable slurmctld && sudo systemctl start slurmctld
These slurmd configs need to be copied again to all the nodes. And slurmd service should be enabled.
$ sinfo # lists the available paritions for job submission
PARTITION AVAIL TIMELIMIT NODES STATE NODELIST
shortjob* up infinite 3 idle rpi[2,4,7]
Temporary Shared partition solution
Since there is no shared directory at the moment, stderr are puked.
slurmstepd-rpi2: error: couldn't chdir to `/home/rpi7/shared': No such file or directory: going to /tmp instead
To temporarly overcome this we cause use sshfs to fusemount a partition
from one of the node to all the other.
On the head node,
mkdir /tmp/shared
cd /tmp/shared && touch this_is_main.txt
On the compute nodes,
apt install sshfs
mkdir /tmp/shared && cd /tmp/
sshfs rpi2@rpi2:/tmp/shared /tmp/shared
cd shared && ls
Do the above for all the nodes and now check the logs, /var/log/slurm/slurmd.log
[2026-02-13T09:00:17.043] [16.batch] error: Controller memory is not enabled!
[2026-02-13T09:00:17.182] [16.batch] done with step
[2026-02-13T09:03:16.007] Launching batch job 19 for UID 1000
[2026-02-13T09:03:16.035] [19.batch] error: Controller memory is not enabled!
Setting up UID
From the log it's evident, that slurm executes as user with UID 1000, make sure this is true for all the nodes.
rpi7@rpi7:~ $ id rpi7
uid=1000(rpi7) gid=1000(rpi7) groups=1000(rpi7)
rpi4@rpi4:~ $ id rpi4
uid=1001(rpi4) gid=1001(rpi4) groups=1001(rpi4)
rpi4@rpi6:~ $ id rpi6
uid=1000(rpi6) gid=1000(rpi6) groups=1000(rpi6)
Make sure the users and uid are synced across multiple node, here only 6, 7 is going to work on a shared directory!
Link for user_group identification.
Best gid/uid practice
$ sudo sysctl net.ipv4.ip_forward=1
$ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
$ sudo iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT
$ sudo iptables -A FORWARD -i wlan0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT