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

Hi,

I propose to discuss about a read only system for OH with a raspberry pi 3 on raspbian jessie.

I know there already a discussion on the community but here, I want to group all informations and then create a tuto.

I’ve already an OH which is running in read only filesystem and want to see if we could enhanced that.

For the moment, I’m running an OH which is link to my master OH by MQTT. This secondary OH is used for security only.

Someone interested ?

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.

Please, Could you tell me what are the problems you encounter ?

I want to keep a lighweight hardware (no hDD, usb stick, etc …), with a better lifespan for sdcard.

Again can you give some clue. From my point of view security, turns around: secured access, have more electric autonomy, etc …

I never try to see how long a sdcard is ok with OH. But I agree, the more larger the sd-card is , more lifespan is.

tmpfs and remount in “rw” sometimes, reduce the difficulty to run OH without reduce the sd card’s lifespan.

Thank you for your point of view.

Gilles.

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.

2 Likes

Hi,

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:

I Follow this tutorial to have a raspberry pi in RO: RPi 3 in read only filesystem

I follow a thread on OH community and modify it to be helpful. I create a systemd init to use my script.

Systemd init:

`
[Unit]
Description=Open Home Automation Bus
After=multi-user.target

 [Service]
 Type=forking
 GuessMainPID=yes
 Restart=always
 RestartSec=20
 ExecStartPre=/opt/openhab/run_once_before_openhab.sh
 ExecStart=/opt/openhab/start.sh
 ExecStop=/usr/bin/kill -SIGINT $MAINPID
 ExecStopPost=/opt/openhab/before_stop_OH.sh

User=root
Group=daemon
WorkingDirectory=/opt/openhab

[Install]
WantedBy=multi-user.target

`
script to launch in first before OH:

#!/bin/bash

OH_USER=root
OH_DIR=/opt/openhab
USER_HOME=/root
TEMP_OH_DIR=/tmp/OH

 mount -o remount,rw /
 tmpfsfiles="/$OH_USER/.eclipse $OH_DIR/workspace/.metadata $OH_DIR/logs $OH_DIR/webapps/static/version"
 for file in $tmpfsfiles; do
         rm -r "$file"
         dashfile="${file//\//_}"
         ln -s "$TEMP_OH_DIR/$dashfile" "$file"
 done


tmpfsfiles="$USER_HOME/.eclipse $OH_DIR/workspace/.metadata $OH_DIR/logs"
 for file in $tmpfsfiles; do
        dashfile="${file//\//_}"
        mkdir "$TEMP_OH_DIR/$dashfile"
done


if [ -d "$TEMP_OH_DIR/etc/zwave" ]
then
    echo "Directory /tmp/OH/etc/zwave exists."
else
mkdir -p $TEMP_OH_DIR/ 
mkdir -p $TEMP_OH_DIR/etc
mkdir -p $TEMP_OH_DIR/etc/zwave
fi

if [ -d "$TEMP_OH_DIR/etc/rrd4j" ]
then
    echo "Directory /tmp/OH/etc/rrd4j exists."
else
mkdir -p $TEMP_OH_DIR/
mkdir -p $TEMP_OH_DIR/etc
mkdir -p $TEMP_OH_DIR/etc/rrd4j
fi


cp $OH_DIR/etc/zwave/* $TEMP_OH_DIR/etc/zwave
cp $OH_DIR/etc/rrd4j/* $TEMP_OH_DIR/etc/rrd4j 
rm -rf $OH_DIR/etc/zwave
rm -rf $OH_DIR/etc/rrd4j
ln -s $TEMP_OH_DIR/etc/zwave $OH_DIR/etc/zwave
ln -s $TEMP_OH_DIR/etc/rrd4j $OH_DIR/etc/rrd4j
mount -o remount,ro /

`

and the stop after OH stop:

#!/bin/bash

USER_HOME=/root
OH_DIR=/opt/openhab
TEMP_OH_DIR=/tmp/OH


 mount -o remount,rw /
 rm  $OH_DIR/etc/zwave
 rm $OH_DIR/etc/rrd4j
 mkdir $OH_DIR/etc/zwave 
 mkdir $OH_DIR/etc/rrd4j

 cp $TMP_OH_DIR/zwave/*.xml $OH_DIR/etc/zwave/
 cp $TMP_OH_DIR/rrd4j/* $OH_DIR/etc/rrd4j/

 rm -rf /tmp/OH

mount -o remount,ro /

`

It’s a bit dirty, as root launch OH and have to create one script instead of many scripts.

PS for Richard: If you have time see my issue in your github for sensorReporter about TLS. I think it’s not too heavy to add it.

Thank you again.