Enable Read Only Themes

I have had some problems in general with the way a gateway backup restore handles themes, and they have been a pain in my side for a while.

Essentially the way that a backup restore handles projects, is that if inside of the backup the projects/ directory doesnt exist, it will just do nothing. It will leave the projects directory present on the gateway, and so you can use this to just version control a gateways config, but not the project (if thats managed elsewhere)

However the themes directory unfortunately doesnt work the same way. It seems that on a backup restore, if you have themes it replaces them (which is expected), but if you remove modules/ out of it, then Ignition will assume that it knows the best and just create the files that it would on a regular startup.

This makes it a pain when you have a bunch of theme files you are version controlling, but need to use gateway backups at times to restore your config (which is huge in containers)

I am deploying a handful of gateways in a k8s cluster, and in an ideal world I would have a PVC that contains the contents of my data/modules/com.inductiveautomation.perspective directory, and is mapped into it as such.

Problem is in most scenarios with a container, you have to restore a backup, and so Ignition will do its unceremonious deletion of your files.

So instead I tried to set the PVC to readOnly so that Ignition can't delete, and I figured I could just ignore whatever errors that throws in the logs.

However it causes the gateway to fault :slightly_frowning_face:

│ jvm 1    | 2024/10/07 23:27:44 | E [IgnitionGateway               ] [23:27:44.485]: Error during context startup.                                                    │
│ jvm 1    | 2024/10/07 23:27:44 | java.io.FileNotFoundException: /usr/local/bin/ignition/data/modules/com.inductiveautomation.perspective/fonts/MonaSans/Mona-Sans.wo │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.io.FileOutputStream.open0(Native Method)                                                                      │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.io.FileOutputStream.open(Unknown Source)                                                                      │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.io.FileOutputStream.<init>(Unknown Source)                                                                    │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.io.FileOutputStream.<init>(Unknown Source)                                                                    │
│ jvm 1    | 2024/10/07 23:27:44 |     at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.doBasicRestore(RedundancyManagerImpl.java:899)     │
│ jvm 1    | 2024/10/07 23:27:44 |     at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.startup(RedundancyManagerImpl.java:304)            │
│ jvm 1    | 2024/10/07 23:27:44 |     at com.inductiveautomation.ignition.gateway.IgnitionGateway.initRedundancy(IgnitionGateway.java:777)                            │
│ jvm 1    | 2024/10/07 23:27:44 |     at com.inductiveautomation.ignition.gateway.IgnitionGateway.lambda$initInternal$1(IgnitionGateway.java:700)                     │
│ jvm 1    | 2024/10/07 23:27:44 |     at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngi │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)                                                │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.util.concurrent.FutureTask.run(Unknown Source)                                                                │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)                           │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)                                                  │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)                                                 │
│ jvm 1    | 2024/10/07 23:27:44 |     at java.base/java.lang.Thread.run(Unknown Source)                                                                               │
│ jvm 1    | 2024/10/07 23:27:44 | I [IgnitionGateway               ] [23:27:44.487]: Ignition[state=STARTING] ContextState = FAULTED

Any ideas on what to do here? I know one possible path is to inject the themes into your gateway backups, which I can do, but is very hacky for what I am doing here because I dont exactly know when to do that in my pipeline

Okay, I dont like this answer, but technically it sort of worked for my specific purpose.

Instead of having the gateway be attached to the PV, instead I just added an initContainer that injected my themes into the backup before it gets restored. That initContainer is attached to the PV, and makes sure I am up to date (on startup)

cp /backups/${GWBK_NAME} /data/restore.gwbk
zip -d /data/restore.gwbk "modules/*" "modules" "projects/*" "projects" || true
zip -ur /data/restore.gwbk /modules/com.inductiveautomation.perspective/themes/
zip -ur /data/restore.gwbk /modules/com.inductiveautomation.perspective/icons/
zip -ur /data/restore.gwbk /modules/com.inductiveautomation.perspective/fonts/

Where this doesnt really work in a scalable sense, is not essentially themes are only refreshed on startup. Which, in 8.3 with API endpoints to trigger this refresh would be good, but unfortunately will still need this in 8.1 for quite some time.