Exchange informations about RPI3 read only file system in the aim to add it in the wiki

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.