Ubuntu Touch persistent configurations
This is a description of how to create persistent configurations in Ubuntu Touch. This resolves the issue where advanced users lose their custom Linux configurations after an "OTA" (Over The Air update). Using a "remount-hack-remount" cycle to achieve changes to the distribution's Read Only area has the drawback that those changes will be lost every time there is an OTA.
Essentially the process of configuring many components of the operating system's distribution (which is largely held on Read Only storage) relies on overlaying the relevant folders/files. Overlaying means to leave the original folder/files where they are and replace all run-time references with pointers to user writeable folders/files.
In Ubuntu Touch user writeable configuration information is saved, by convention, in particular part of the file system: /userdata/. A key feature of /userdata/ is that it is not affected by an OTA.
In general user modified configurations which are part of the distribution's Read Only data can be copied to the userdata area. Really all that needs to happen is that the original configuration information from the Read Only area must to be copied in to the /userdata/ writeable area. Changes are then made to the /userdata/ copy.
The only remaining challenge is to make the operating system use the /userdata/ version. In Ubuntu Touch this is performed by the linux "mount" command with the "bind" option when the phone boots. Essentially a script (/init) executes a series of bind mount commands and sets up the file system layout prior to most stuff starting up.
Bind mount operation Remount part of the file hierarchy somewhere else. The call is: mount --bind olddir newdir or by using this fstab entry: /olddir /newdir none bind After this call the same contents are accessible in two places. It is important to understand that "bind" does not to create any second-class or special node in the kernel VFS. The "bind" is just an‐ other operation to attach a filesystem. There is nowhere stored information that the filesystem has been attached by "bind" operation. The olddir and newdir are independent and the olddir may be umounted. One can also remount a single file (on a single file). It's also possible to use the bind mount to create a mountpoint from a regular di‐ rectory, for example: mount --bind foo foo The bind mount call attaches only (part of) a single filesystem, not possible submounts. The entire file hierarchy including submounts is attached a second place by using: mount --rbind olddir newdir Note that the filesystem mount options maintained by kernel will remain the same as those on the original mount point. The userspace mount options (e.g. _netdev) will not be copied by mount(8) and it's necessary explicitly specify the options on mount command line. mount(8) since v2.27 allows to change the mount options by passing the relevant options along with --bind. For example: mount -o bind,ro foo foo This feature is not supported by the Linux kernel; it is implemented in userspace by an additional mount(2) remounting system call. This solution is not atomic. The alternative (classic) way to create a read-only bind mount is to use the remount operation, for example: mount --bind olddir newdir mount -o remount,bind,ro olddir newdir Note that a read-only bind will create a read-only mountpoint (VFS entry), but the original filesystem superblock will still be writable, meaning that the olddir will be writable, but the newdir will be read-only. It's also possible to change nosuid, nodev, noexec, noatime, nodiratime and relatime VFS entry flags by "remount,bind" operation. The an‐ other (for example filesystem specific flags) are silently ignored. It's impossible to change mount options recursively (for example with -o rbind,ro). mount(8) since v2.31 ignores the bind flag from /etc/fstab on remount operation (if "-o remount" specified on command line). This is nec‐ essary to fully control mount options on remount by command line. In the previous versions the bind flag has been always applied and it was impossible to re-define mount options without interaction with the bind semantic. This mount(8) behavior does not affect situations when "remount,bind" is specified in the /etc/fstab file.
The init script will bind mount a list of directories contained in the file:
/etc/system-image/writable-paths
which is essentially a list of all system data that can be modified or customised by a user.
# 1st column: Mount point # 2nd column: Path relative to root of persistent storage (or auto) # 3rd column: type => persistent|synced|temporary # 4th column: action => none|transition (transition requires persistent) # 5th column: mount flags /android/data android-data persistent none none /etc/NetworkManager/system-connections auto persistent none none /home user-data persistent transition none /media auto temporary none defaults /opt/click.ubuntu.com auto persistent transition none /tmp none temporary none defaults /etc/cups auto persistent none none /var/spool/cups auto persistent none none /var/cache/cups auto temporary none defaults /var/crash auto persistent none none /var/lib/AccountsService/users auto persistent none none /var/lib/aethercast auto persistent none none /var/lib/biometryd-meizu-fp-reader auto persistent none none /var/lib/dbus auto persistent none none /var/lib/extrausers auto persistent transition none /var/lib/logrotate auto persistent none none /var/lib/NetworkManager auto persistent none none /var/lib/ofono auto persistent none none /var/lib/openvpn/chroot/tmp auto temporary none defaults /var/lib/PackageKit auto persistent none none /var/lib/bluetooth auto persistent none none /var/lib/lightdm auto persistent none none /var/lib/lightdm-data auto persistent none none /var/lib/sudo auto temporary none defaults,mode=0700 /var/lib/system-image auto persistent none none /var/lib/systemd auto synced none none /var/lib/upower auto persistent none none /var/lib/usermetrics auto persistent none none /var/lib/ubuntu-location-service auto persistent none none /var/log auto persistent transition none # ufw /etc/default/ufw auto persistent transition none /etc/ufw auto persistent transition none /lib/ufw/user6.rules auto persistent transition none /lib/ufw/user.rules auto persistent transition none # apparmor cache is pregenerated in the image builds /etc/apparmor.d/cache auto persistent transition none # needed by click-apparmor - use transition since some core apps are # pre-installed on the image /var/cache/apparmor auto synced none none /var/lib/apparmor auto synced none none # for a writable dconf db used by customization /custom/etc/dconf auto persistent none none # ssh /etc/ssh auto persistent transition none /etc/init/ssh.override auto persistent transition none # sudoers.d /etc/sudoers.d auto persistent transition none # used for various writable files (timezone, localtime, ...) /etc/writable auto synced none none # ureadahead /var/lib/ureadahead auto persistent transition none # apport /var/lib/apport auto persistent transition none # allow us to disable apport as it slows down image /etc/default/apport auto persistent none none # needed for rfkill persistance /var/lib/rfkill auto persistent transition none # needed for urfkill persistance /var/lib/urfkill auto persistent transition none # needed for usb tethering /var/lib/misc auto persistent transition none # needed to persist ntp enabled/disabled /etc/network/if-up.d auto persistent transition none # snappy /snap auto persistent transition none /var/lib/snapd auto persistent transition none # nfcd /var/lib/nfcd auto persistent transition none # Waydroid /var/lib/waydroid auto persistent none none
The first thing to note here is that I have added the lines about the sudoers.d directory. The effect of that is to ensure that the directory in /userdata/systemdata/etc/sudoers.d "overlays" the real /etc/sudoers.d.
Before attempting to change the bind list, don't forget to make the system-image writable as per the SDcard HowTo page. Remember to reset things back to RO afterwards.
Experimental evidence shows that an OTA does not appear to modify the list of directories needing bind mounting.
Clearly making changes to the bind mount list requires a remount-hack-remount cycle as outlined in the SDCARD Howto page since the list lives on the Read Only area of storage (making it something of an exception to the rule).
Note: As pointed out by Tobiyo Kuujikai, while this may remain persistent for the DEV channel, it may well not remain so for the Release Candidate and Stable channels. Further testing with these channels shows the suspicion is indeed correct. The data in the replicated folder(s) remain intact over OTAs, however the /etc/system-image/writeable-paths list file is overwritten by the RC and Stable channel's more complete update.
Update: (1 Dec 2021) With release candidate version 2 the persistent data in /etc/system-image/writable-paths was *not* overwritten. Nevertheless the modifications/additions to the writable-paths were saved in to /userdata/system-data/etc/system-images/writable-path-customization-to-be-added just in case.
Note: (3 Dec 2021) Tobiyo Kuujikai: "Ah I see what you noticed, yes it is not always overwritten. As ota updates are delta updates When that delta is too large to be determined it will trigger what we call a "full ota update" and be overwritten however"
At this point the OTA process is something of a russian roulette as far as customizations to /etc/system-image/writable-paths is concerned. While it's not hard to recover it's hardly ideal.