Hello,
my personal opinion on that: Minimizing write access is a good idea, blocking all write access does however have too many downsides to it.
What are you trying to accomplish by doing so? Extend the SD card lifespan? Security?
The reduced usability of the system does not justify these goals. Security can be accomplished in better ways and the lifespan of the SD card can be largely extended by using a bigger SD card (to distribute wear) and tmpfs (in combination with reduced logging). Having played with locked systems I made the experience that this creates more problems than benefits.
I’ve been running all my Pis with a ReadOnly FS and so far I’ve not found them to be that onerous to use. It all depends on your use patterns though. My Pis are remote devices, not really operational servers. They don’t run OH nor any real servers and instead just report sensor values or trigger activators. In that usage pattern a RO file system is actually not much of a problem at all.
In short, I agree with @ThomDietrich, it is probably more of a hassle than it is worth for any server that you log into and use on a daily basis. However, for any machine that just sits and runs unattended for days or weeks at a time, or one that could lose power without being shutdown gracefully it just might be worth the hassle.
I setup rsyslog to log to a remote server (running in Docker on my main non-read only server) and put all local writes on a tempfs volume. Others write them to the local SD card once an hour or so instead using a cron job. Everything else is mounted ro. All I had to do to make it useful from that point was to add an rw and ro alias to my shell configs and add some remount rw/remount ro commands to the few cron jobs that need to write to the main file system (e.g. fake-hwclock, Tripwire, etc.) I even updated my prompt to use color and unicode symbols to tell me what state the file system is in on my prompt.
NOTE: I use fish for my shell (it’s kind of cool, you should check it out) but you should be able to accomplish the same or near the same in bash.
I’m not quite sure what you are trying to say here. From a security perspective, a RO filesystem provides some protection because it makes it more difficult for a non-root user to install a persistent exploit on the machine because the only parts of the system they are allowed to write to disappear upon a system reboot. NOTE: This only works if you either monitor (e.g. Tripwire) or reboot the Pi periodically to clear out the tempfs filesystems. Also, if the attacker has root a RO filesystem doesn’t help at all.
From a reliability perspective, besides helping to avoid wearing out your SD card, it makes the Pi more resilient to hard power resets (i.e. pulling the plug while it is running). Sudden loss of power while writing to the SD card (or any other media) has a small but real potential to corrupt the filesystem. If the Pis can’t write to disk, they can’t corrupt the persistent file systems.
I’ve posted my Ansible scripts that set up my Pi for RO previously here:
Here is an update that includes adding some remounts for additional cron jobs that need rw on the root file system.
---
- name: Add aliases and fancy prompt to show status of FS
blockinfile:
state: present
dest: /etc/bash.bashrc
block: |
# set variable identifying the filesystem you work in (used in the prompt below)
set_bash_prompt(){
fs_mode=$(mount | sed -n -e "s/^\/dev\/.* on \/ .*(\(r[w|o]\).*/\1/p")
PS1='\[\033[01;32m\]\u@\h${fs_mode:+($fs_mode)}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
}
alias ro='sudo mount -o remount,ro / ; sudo mount -o remount,ro /boot'
alias rw='sudo mount -o remount,rw / ; sudo mount -o remount,rw /boot'
# setup fancy prompt"
PROMPT_COMMAND=set_bash_prompt
- name: Set up /tmp, /var/log, and /var/tmp as tempfs
blockinfile:
state: present
dest: /etc/fstab
insertafter: "# use dphys-swapfile swap[on|off] for that"
block: |
tmpfs /tmp tmpfs nosuid,nodev 0 0
tmpfs /var/log tmpfs nosuid,nodev 0 0
tmpfs /var/tmp tmpfs nosuid,nodev 0 0
- name: Set permissions on /tmp
file:
mode: a+rwx
path: /tmp
state: directory
- name: Mount /tmp
mount:
name: /tmp
src: /tmp
fstype: tmpfs
state: mounted
- name: Mount /var/log
mount:
name: /var/log
src: /var/log
fstype: tmpfs
state: mounted
- name: Mount /var/tmp
mount:
name: /var/tmp
src: /var/tmp
fstype: tmpfs
state: mounted
- name: Remap folders to /tmp
script: relink.sh
- name: Waiting for {{ inventory_hostname }} to come back from reboot
local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300
become: false
- name: Configure boot command line
replace:
dest: /boot/cmdline.txt
regexp: 'otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait'
replace: 'otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait fastboot noswap ro'
- name: Move dhcpd lock file to temp
replace:
dest: /etc/systemd/system/dhcpcd5
regexp: '\=/run/dhcpcd.pid'
replace: '\=/var/run/dhcpcd.pid'
- name: update fake-hwclock cron job to remount / rw and then mount it back
copy:
dest: /etc/cron.hourly/fake-hwclock
src: fake-hwclock
- name: Check for presence of Tripwire cron job
stat: path=/etc/cron.daily/tripwire
register: tripwire_cron
- name: Update cron job to remount rw before running check
copy:
dest: /etc/cron.daily/tripwire
src: tripwire-cron
when: tripwire_cron.stat.exists == True
- name: Update apt daily script to remount rw before running
copy:
dest: /etc/cron.daily/apt
src: apt
- name: Update dpkg daily script to remount rw before running
copy:
dest: /etc/cron.daily/dpkg
src: dpkg
- name: Update logrotate daily script to remount rw before running
copy:
dest: /etc/cron.daily/logrotate
src: logrotate
- name: Update man-db daily script to remount rw before running
copy:
dest: /etc/cron.daily/man-db
src: man-db
- name: Update passwd daily script to remount rw before running
copy:
dest: /etc/cron.daily/passwd
src: passwd
- name: Remove some start scripts
shell: /sbin/insserv -r bootlogs; /sbin/insserv -r console-setup
- name: Set boot FS as readonly
replace:
dest: /etc/fstab
regexp: '/dev/mmcblk0p1 /boot vfat defaults 0 2'
replace: '/dev/mmcblk0p1 /boot vfat defaults,ro 0 2'
- name: Set root as readonly
replace:
dest: /etc/fstab
regexp: '/dev/mmcblk0p2 / ext4 defaults,noatime 0 1'
replace: '/dev/mmcblk0p2 / ext4 defaults,noatime,ro 0 1'
- name: Reboot as read only
include: tasks/reboot.yml
NOTE: I have plans to publish all my Ansible scripts to github sometime, but don’t hold your breath, lots of other priorities are higher right now.
If anyone is curious, I can post my fish_prompt.fish file (the bash changes are at the top of the ansible script) for those who want to play with that. In addition to the symbols for RW/RO status of the symbol, it also tells you which branch you are in and whether there are uncommitted changes when you are in a git repo folder.
Thank you Richard for you answer and thank you for what you do on this community.
To sum up, it is possible to have an OH without writing if :
sensor report
trigger activators
a server is present to store and log everything if needed.
So, it isn’t possible to do a read only OH server without an external device like personal server or cloud or HDD.
It is really interesting to have remote OH with a server OH in some cases. Like you Richard or in my case to have an OH which is used for security (PIR sensors , siren, etc…). Using MQTT is very usefull to do this.
Thank you for your points of view.
Here is what I do to have a remote OH in read only: