I have been trying to document the process of configuring a Raspberry Pi as a Time Machine Capsule, but the article became far too long. It covered far too much information and was really hard to read.
I then decided to break the stages into more manageable steps. This has the advantage of allowing the common stages, like setting up the OS, to be shared between different projects.
Therefore, this is that first entry. Some others will follow about how to build different things from this first base image.
Selecting the OS
The 64-bit beta release of Raspberry Pi OS I tried didn’t let ZFS install easily. Ubuntu has the advantage of being a like for like experience regardless of the platform, so it is my preferred choice. Any experience you gain with it will be easily transferable.
You can download Ubuntu Server images from https://ubuntu.com/download/raspberry-pi. The LTS version is also the preferred one.
The Raspberry Pi model will determine the supported versions of the OS.
Model | 32-bit Ubuntu | 64-bit Ubuntu |
Raspberry Pi 2 | Supported | Not supported |
Raspberry Pi 3 | Supported | Recommended |
Raspberry Pi 4 | Supported | Recommended |
The Raspberry Pi 3 has limited benefits when using the 64-bit image due to its limited RAM. In addition, it won’t support ZFS for the same reason. The Pi will restart/reset when ZFS volumes are accessed due to a lack of RAM.
If you are going to use a GUI, you should choose a Raspberry Pi 4 with at least 4GB of RAM.
The image can be directly installed on a micro SD card:
# ddrescue -y -c 4Ki ubuntu-20.04.3-preinstalled-server-arm64+raspi.img /dev/sdxx
Installing Ubuntu Server on a USB stick
It is possible to boot from a USB stick, which is preferable for several reasons. They are cheaper, easier to access from another system, and simple to replace.
First, enable USB boot on your Pi.
Model | USB Boot Support | Notes |
Raspberry Pi 1 | Not supported | n/a |
Raspberry Pi 2 and 3B | Supported | On Raspberry Pi OS echo program_usb_boot_mode=1 | sudo tee -a /boot/config.txt and reboot. |
Raspberry Pi 3B+ | Supported | Supported out of the box |
Raspberry Pi 4 | Supported | On Raspberry Pi OS rpi-eeprom-config --edit and set BOOT_ORDER=0xf41 and reboot. |
You might have to boot from an SD card at least once to configure USB boot. Once enabled, it remains activated.
Additional information about the different boot modes for the Raspberry Pi
The following links are provided for reference.
Raspberry Pi booting from USB mass storage https://www.raspberrypi.org/documentation/computers/raspberry-pi.html#booting-from-usb-mass-storage
Raspberry Pi 4 bootloader configuration https://www.raspberrypi.org/documentation/computers/raspberry-pi.html#raspberry-pi-4-bootloader-configuration
Raspberry Pi 4 boot flow https://www.raspberrypi.org/documentation/computers/raspberry-pi.html#raspberry-pi-4-boot-flow
Configuration steps
Once the Pi has been configured to boot from a USB device, install the image on a USB stick like the SD card.
# ddrescue -y -c 4Ki ubuntu-20.04.3-preinstalled-server-arm64+raspi.img /dev/sdxx
For the image to be bootable, you need to make some changes. I extracted the steps from this Raspberry Pi forum post. You might find it easier to apply changes if you mount it on another system.
There are two options to make the changes:
- Mount the USB stick on another system, and then issue the commands on the USB device. This other system can be the Raspberry Pi itself booting from the SD card, and accessing the USB device.
- Or make the changes on the SD card, and then copy the SD card image to the USB device.
Apply the following changes.
1) On the /boot of the USB device, uncompress vmlinuz.
$ cd /media/*/system-boot/
$ zcat vmlinuz > vmlinux
2) Update the config.txt file. The pi4 section is shown in this example, but it has also been tested on a Pi 3. Just enter the information for your Pi model.
$ vim config.txt
The dtoverlay line might be optional for headless systems, but if you have the time and inclination, there is some documentation regarding Raspberry Pi’s device tree parameters.
[pi4]
kernel=vmlinux
max_framebuffers=2
dtoverlay=vc4-fkms-v3d
boot_delay
initramfs initrd.img followkernel
3) Create a script in the boot partition called auto_decompress_kernel with the following content:
#!/bin/bash -e
## Set Variables
BTPATH=/boot/firmware
CKPATH=$BTPATH/vmlinuz
DKPATH=$BTPATH/vmlinux
## Check if compression needs to be done.
if [ -e $BTPATH/check.md5 ]; then
if md5sum --status --ignore-missing -c $BTPATH/check.md5; then
echo -e "\e[32mFiles have not changed, Decompression not needed\e[0m"
exit 0
else
echo -e "\e[31mHash failed, kernel will be compressed\e[0m"
fi
fi
# Backup the old decompressed kernel
mv $DKPATH $DKPATH.bak
if [ ! $? == 0 ]; then
echo -e "\e[31mDECOMPRESSED KERNEL BACKUP FAILED!\e[0m"
exit 1
else
echo -e "\e[32mDecompressed kernel backup was successful\e[0m"
fi
#Decompress the new kernel
echo "Decompressing kernel: "$CKPATH".............."
zcat $CKPATH > $DKPATH
if [ ! $? == 0 ]; then
echo -e "\e[31mKERNEL FAILED TO DECOMPRESS!\e[0m"
exit 1
else
echo -e "\e[32mKernel Decompressed Succesfully\e[0m"
fi
# Hash the new kernel for checking
md5sum $CKPATH $DKPATH > $BTPATH/check.md5
if [ ! $? == 0 ]; then
echo -e "\e[31mMD5 GENERATION FAILED!\e[0m"
else
echo -e "\e[32mMD5 generated Succesfully\e[0m"
fi
# Exit
exit 0
Normally you would need to mark the script as executable, but unless you modify the partition from its FAT32 default, there is no executable flag to set. So leave it as it is.
If you can mount the root filesystem in the system you are using to edit the files, you can go ahead with steps 4 and 5. Otherwise, you should be able to boot now and manually do these steps after your first boot.
4) Create a script in /ect/apt/apt.conf.d/ directory and call it 999_decompress_rpi_kernel
# cd /media/*/writable/etc/apt/apt.conf.d/
# vi 999_decompress_rpi_kernel
Fill the file with the following content:
DPkg::Post-Invoke {"/bin/bash /boot/firmware/auto_decompress_kernel"; };
5) Make the script executable.
# chmod 744 999_decompress_rpi_kernel
You can save yourself some time and configure the network at this stage.
In my case, I have a static DHCP lease associated with the Pi MAC address, but if you don’t, you can configure the network with a static IP address by editing the network-config file in /boot.
$ cd /media/*/boot/
$ vim network-config
An example of a static address entry would be:
version: 2
ethernets:
eth0:
dhcp4: no
addresses: [192.168.1.201/24]
gateway4: 192.168.1.254
nameservers:
addresses: [192.168.1.254]
You can eject the USB drive, insert it on your Raspberry Pi and boot.
Setting up Ubuntu
The default user name and password are ubuntu / ubuntu.
Upon login, you will be asked to change your password. We will delete this user in the following steps to increase security.
Run an update:
$ sudo su
# apt update
# apt upgrade
Setting up users
Create a new user (or change the name of the existing user).
# adduser <newuser>
Extract the groups for the user ubuntu and compare them with the new user.
# id ubuntu ; echo ; id <newuser>
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),115(netdev),118(lxd)
uid=1001(newuser) gid=1001(newuser) groups=1001(newuser)
Add the new user to the same groups.
# usermod -a -G adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev,lxd newuser
Hostname
Set the hostname of your choice.
# hostnamectl set-hostname <system_name>
[Check the change]
# hostnamectl
Static hostname: pi-capsule
Icon name: computer
Machine ID: db0a1818241a47e178f229294f6864ae
Boot ID: 983818fbaa8246348066c36f2237636e
Operating System: Ubuntu 20.04.2 LTS
Kernel: Linux 5.4.0-1029-raspi
Architecture: arm64
Date and time
Set the time zone.
# timedatectl set-timezone Europe/London
Configure the time sources by editing /etc/systemd/timesyncd.conf.
[Time]
NTP=uk.pool.ntp.org
FallbackNTP=ntp.ubuntu.com
Restart the service.
# systemctl restart systemd-timesyncd.service
Check the status and check that the time source is correct.
# systemctl status systemd-timesyncd.service
Finally, check that the time zone is correct.
# timedatectl status
Local time: Sun 2021-08-29 23:24:49 BST
Universal time: Sun 2021-08-29 22:24:49 UTC
RTC time: n/a
Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Customising the MOTD
You can get the MOTD from the login screen manually with the following command.
$ for i in /etc/update-motd.d/* ; do if [ "$i" != "/etc/update-motd.d/98-fsck-at-reboot" ]; then $i; fi; done
To get system information (including temperature):
$ /etc/update-motd.d/50-landscape-sysinfo
You can edit, add and reorder scripts in /etc/update-motd.d/.
Configuring SSH
SSH will be enabled by default. Test access with the newly created account.
By default, only the password is required to access the server, but we will add the requirement of needing an SSH key with the password. And also limit access only from authorised IP addresses.
If you haven’t generated a public and private key pair on your system (the one used to log into the Pi), you will need to do it (explained below).
A brief note on encryption. Elliptic curve cryptography (ECC) generates smaller keys and provides faster encryption than non-ECC. The smaller ECC keys also provide an equivalent level of encryption provided only with bigger RSA keys:
ECC key size | RSA equivalent |
160 bits | 1024 bits |
224 bits | 2048 bits |
256 bits | 3072 bits |
384 bits | 7680 bits |
512 bits | 15360 bits |
You can use either ECDSA or ED25519 keys. ED25519 isn’t as universally implemented yet due to being quite new, so some clients might not support it, but it is the fastest and most secure one.
For both types of encryption, it is recommended to use the bigger key size. This is 521 bits for ECDSA (note that 521 isn’t a typo). ED25519 keys have a fixed length of 512 bits.
When issuing ssh-keygen, use the -o option. This forces the use of the new OpenSSH format (instead of PEM) when saving your private key. It increases resistance to a known brute-force attack. It breaks compatibility with OpenSSH versions older than 6.5, but this version of Ubuntu runs version 8.2, so this isn’t an issue.
More information about SSH key generation is available here: https://www.ssh.com/ssh/keygen/
The steps are:
Create a suitable key pair with:
$ ssh-keygen -o -t ed25519
[or]
$ ssh-keygen -o -t ecdsa -b 521
Copy the public key to the Ubuntu server. It can be done manually, but it is best to use the appropriate tool:
$ ssh-copy-id -i ~/.ssh/<myprivatekey> <user>@<remotehost>
Note that you use the -i flag with your private key, and ssh-copy-id will send the public key for storage on the remote host.
SSH can be configured on the server side to allow only password logins, only key logins, or to require both.
# vim /etc/ssh/sshd_config
“PasswordAuthentication no” will only use the key, and “PasswordAuthentication yes” will use both password and key. Obviously, the second option is safer.
We also disable the option to allow root to login via SSH. The root account is disabled on the image by default, but ensure SSH has been configured correctly anyway.
PermitRootLogin no
PasswordAuthentication yes
# systemctl restart sshd
SSH from another terminal with the new user account, and ensure that the access is working.
If it works, delete the old ubuntu account.
# userdel -r ubuntu
Activate and configure the firewall
Set default rules (deny all incoming, allow all outgoing).
# ufw status
# ufw default allow outgoing
# ufw default deny incoming
UFW requires IPv6 to be enabled. It can be made to work with it disabled, but how to achieve that is out of the scope of this post.
# vim /etc/default/ufw
IPV6=yes
Allow SSH.
# ufw allow ssh
[but preferably allow only specific clients:]
# ufw allow proto tcp from <SOURCE> to <SERVER> port 22
And limit the allowed connection attempts to thwart brute force attacks:
ufw limit ssh
Enable the firewall and check the rules:
# ufw enable
# ufw status
[List rules with numbers]
# ufw status numbered
Remember that if you are using IPv6, you might need to edit rules accordingly.
Install log2ram
To reduce the number of writes on the USB drive/SD card, you can use the RAM disk utility log2ram.
https://github.com/azlux/log2ram
Not only that, it will speed up the performance of the Raspberry Pi in exchange for a small amount of RAM.
Install:
# echo "deb http://packages.azlux.fr/debian/ buster main" | sudo tee /etc/apt/sources.list.d/azlux.list
# wget -qO - https://azlux.fr/repo.gpg.key | sudo apt-key add -
# apt update
# apt install log2ram
Configure the service. The SIZE entry depends on your system; 256M is a lot for a Pi with only 1GB of RAM.
# vim /etc/log2ram.conf
SIZE=256M
USE_RSYNC=true
MAIL=true
PATH_DISK="/var/log"
And restart.
# reboot
Check that the service is working:
$ systemctl status log2ram
$ df -h | grep log2ram
log2ram 256M 106M 151M 42% /var/log
Installing additional utilities
Install your choice of applications.
# apt install mosh tmux pydf vim-nox glances iotop
Mosh might require some ports to be opened in the firewall.
The range of ports goes from 60001 to 60999, but if you are expecting few connections, you can make the range smaller.
# ufw allow proto udp from <SOURCE> to <SERVER> port 60001:60010
# ufw limit 60001:60010/udp
Install Cockpit
# apt install -y cockpit
# ufw allow proto tcp from <SOURCE< to <SERVER> port 9090
# ufw limit 9090/tcp
The system can now be reached via the web browser via port 9090:
https://<hostname/IP>:9090
Other customisation
Argon Fan HAT configuration
If you have an Argon fan HAT, you can configure it as follows.
$ curl https://download.argon40.com/argonfanhat.sh -o argonfanhat.sh
$ bash argonfanhat.sh
[...]
Use argonone-config to configure fan
Use argonone-uninstall to uninstall
I have configured with the following triggers.
- 30 ºC -> 0%
- 60 ºC -> 10%
- 65 ºC -> 25%
- 70 ºC -> 55%
- 75 ºC -> 100%
Aliases
On Ubuntu, and most distros, there will be an entry in ~/.bashrc that will look like this:
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
This entry can be added manually if not present. This allows all of the aliases to be grouped in ~/.bash_aliases.
$ vim ~/.bash_aliases
# Show free RAM
alias showfreeram="free -m | sed -n '2 p' | awk '{print $4}'"
# Release and free up RAM
# alias freeram='freeram && sync && sudo echo 3 | sudo tee /proc/sys/vm/drop_caches && freeram -m'
# Show temperature
alias temp='cat /sys/class/thermal/thermal_zone0/temp | head -c -4 && echo " C"'
# Show ZFS datasets compress ratios
alias ratio='sudo zfs get all | grep " compressratio "'
This would create a base image with a decent level of security. I will likely add how to add Fail2Ban to improve security even further.