This is what "we" (Mr GPT and I) got after a LOT of messing around.
It got into hallucination mode and went around quite a number of times with the same reply.
Then I started to better understand what it was TRYING to do and tried a few things myself.
Sorry, this code has a path set for my TEST case but is easily modified.
Code - no foreigners in it.
[{"id":"5eea7184f98a4c51","type":"exec","z":"e891eac8.28aea8","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"List reboot files (newest first)","x":640,"y":2830,"wires":[["ac0cc1d229a3949c"],[],[]]},{"id":"f8c34d36176bb3de","type":"template","z":"e891eac8.28aea8","name":"cmd","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"find {{{basePath}}} -maxdepth 1 -type f ! -name 'last_alive.db' -printf '%f\\n' | sort -r","output":"str","x":430,"y":2830,"wires":[["9521311b0c077153","5eea7184f98a4c51"]]},{"id":"ac0cc1d229a3949c","type":"function","z":"e891eac8.28aea8","name":"Tag reboot file","func":"// Tag reboot file lines and prepare for merging\n\n// split payload into lines, trim whitespace, remove empty lines\nlet lines = msg.payload.split(/\\r?\\n/).map(l => l.trim()).filter(l => l !== '')\n\n// prepend 'Rebooted at ' only if not already present\nlines = lines.map(l => l.startsWith('Rebooted at') ? l : `Rebooted at ${l}`)\n\n// rejoin lines into single payload\nmsg.payload = lines.join('\\n')\n\n// mark this message as order 1 (after uptime)\nmsg.order = 1\n\n// if no lines left, mark as empty so Merge node can skip it\nif (lines.length === 0) {\n msg._emptyForMerge = true\n}\n\nreturn msg\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":2830,"wires":[["018e232e3d83ec77","5219da2fc0395841"]]},{"id":"ed5f05a749fdb98b","type":"junction","z":"e891eac8.28aea8","x":310,"y":2860,"wires":[["f8c34d36176bb3de","45b84c6df27ea117","c53daecf90b70618"]]},{"id":"9521311b0c077153","type":"debug","z":"e891eac8.28aea8","name":"debug 18","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":2780,"wires":[]},{"id":"018e232e3d83ec77","type":"function","z":"e891eac8.28aea8","name":"Merge last_alive + reboots","func":"// --- Explicit context wipe ---\nif (msg.wipe === true) {\n context.set('store', {})\n node.warn('Context wiped via msg.wipe')\n return null\n}\n\n// --- Get or initialise context store ---\nlet store = context.get('store') || {}\n\n// --- Require valid order ---\nif (msg.order !== 0 && msg.order !== 1) {\n node.warn('Invalid or missing msg.order')\n return null\n}\n\n// --- Store message by order (arrival order irrelevant) ---\nstore[msg.order] = RED.util.cloneMessage(msg)\ncontext.set('store', store)\n\n// --- Wait until both messages are present ---\nif (!store[0] || !store[1]) {\n return null\n}\n\n// --- Prepare output messages ---\nconst msg0 = RED.util.cloneMessage(store[0])\nconst msg1 = RED.util.cloneMessage(store[1])\n\nmsg0.topic = 'uptime_and_reboots'\nmsg1.topic = 'uptime_and_reboots'\n\n// --- Send BOTH messages from ONE output ---\nnode.warn('Sending msg.order 0 then msg.order 1')\nreturn [[msg0, msg1]]\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1120,"y":2860,"wires":[["9f41501f4fbd6cb7"]]},{"id":"5219da2fc0395841","type":"debug","z":"e891eac8.28aea8","name":"msg1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":910,"y":2790,"wires":[]},{"id":"378e0fe5313bc482","type":"change","z":"e891eac8.28aea8","name":"","rules":[{"t":"set","p":"basePath","pt":"msg","to":"/media/pi/06BB-C87D/logs/reboot/2025-06/","tot":"str"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":200,"y":2860,"wires":[["ed5f05a749fdb98b"]]},{"id":"45b84c6df27ea117","type":"template","z":"e891eac8.28aea8","name":"file","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{{basePath}}}last_alive.db","output":"str","x":430,"y":2890,"wires":[["a06b52bf2cd0ada2","5d69e6136bd43607"]]},{"id":"c53daecf90b70618","type":"debug","z":"e891eac8.28aea8","name":"base path","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"basePath","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":2940,"wires":[]},{"id":"aa1cd627e0d32f80","type":"function","z":"e891eac8.28aea8","name":"Extract uptime","func":"// extract line starting with 'up ' from last_alive.db\nconst lines = msg.payload.split(/\\r?\\n/)\nconst uptime = lines.find(l => l.startsWith('up '))\nmsg.payload = uptime || 'uptime not found'\nmsg.order = 0 // last_alive comes first\nreturn msg","outputs":1,"x":850,"y":2890,"wires":[["018e232e3d83ec77","3b146b0e65398225"]]},{"id":"b60a88c8903619e9","type":"inject","z":"e891eac8.28aea8","name":"Wipe","props":[{"p":"wipe","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":890,"y":2990,"wires":[["018e232e3d83ec77"]]},{"id":"9f41501f4fbd6cb7","type":"debug","z":"e891eac8.28aea8","name":"Final output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1080,"y":2920,"wires":[]},{"id":"09e98bbe33888ffa","type":"inject","z":"e891eac8.28aea8","name":"Trigger","props":[{"p":"basePath","v":"/media/pi/06BB-C87D/logs/reboot/2025-06/","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":2810,"wires":[["378e0fe5313bc482"]]},{"id":"a06b52bf2cd0ada2","type":"file in","z":"e891eac8.28aea8","name":"Read last_alive.db","filename":"payload","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"utf8","allProps":false,"x":610,"y":2890,"wires":[["aa1cd627e0d32f80"]]},{"id":"5d69e6136bd43607","type":"debug","z":"e891eac8.28aea8","name":"last alive","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":2930,"wires":[]},{"id":"3b146b0e65398225","type":"debug","z":"e891eac8.28aea8","name":"msg2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":880,"y":2930,"wires":[]}]
NEEDED files:
last_alive.db is the current active file which shows the uptime.
Other files (which may or not exist) are any other files which were renamed if the machinereboots in THIS MONTH ONLY.
At the start of each month, all existing files are moved to their respective month directories. (Or is it just before the end of the month? Does it matter?)
The output is then sent to a ring buffer node and I hope all are on their own line.
The only catch is the sorting of the other files and getting them to be NEWEST AT THE TOP sorting.
So the current (active) is at the top, then a list of all reboots for this month are shown below starting with the most recent to the oldest (at the bottom).
I spent about 3 hours (maybe more) today earlier and got it to this.
This is what I have in my test directory (as per the code)
Files:
last_alive.db 'Rebooted at 2025-06-08 191137.db'
'Rebooted at 2025-06-04 175552.db' 'Rebooted at 2025-06-09 101721.db'
'Rebooted at 2025-06-04 225056.db' 'Rebooted at 2025-06-09 102751.db'
'Rebooted at 2025-06-04 234148.db' 'Rebooted at 2025-06-09 104929.db'
'Rebooted at 2025-06-05 202521.db' 'Rebooted at 2025-06-09 115023.db'
'Rebooted at 2025-06-06 180203.db' 'Rebooted at 2025-06-09 185031.db'
'Rebooted at 2025-06-06 183946.db' 'Rebooted at 2025-06-12 144016.db'
'Rebooted at 2025-06-07 070837.db' 'Rebooted at 2025-06-12 144153.db'
'Rebooted at 2025-06-08 182600.db' 'Rebooted at 2025-06-12 151600.db'
The last_alive.db is a copy of the real one. But as it is needed, I had to put it there.
Basic structure:
cat Rebooted\ at\ 2025-06-04\ 175552.db
2025-06-04 17:53:05
up 35 weeks, 5 days, 22 hours
and
cat last_alive.db
HEARTBEAT
up 19 weeks, 6 days, 13 hours, 46 minutes