| 1 | # Root apply of a platform self-update (spec §6): re-verify the pointer, |
| 2 | # copy the candidate into a root-owned dir, journal, swap the binary and |
| 3 | # units, restart the edge, gate it, roll back to carlos.prev on failure. |
| 4 | # |
| 5 | # ExecStart goes through one line of shell so that when a journal is open |
| 6 | # (a previous apply was cut off) and carlos.prev exists, the RECOVERY runs |
| 7 | # from carlos.prev rather than from the candidate that did not work. |
| 8 | # |
| 9 | # The moves protocol floor (moves spec §11) narrows that choice: once a |
| 10 | # floor is armed (/data/.moves/floor.min-release is there and not empty), |
| 11 | # carlos.prev runs only when its verdict file (/opt/carlos/carlos.prev.allowed, |
| 12 | # written by root after comparing versions in Go) names that same floor, |
| 13 | # byte for byte. A floor path that is a symlink or anything but a regular |
| 14 | # file (a dangling link, a FIFO, a directory) never reads as "no floor": |
| 15 | # the current binary runs (#267). carlos.prev's own floor gate (carlos |
| 16 | # system floor-allows-prev) makes the comparison: it opens each file once, |
| 17 | # without following a symlink or waiting on a FIFO, refuses anything but a |
| 18 | # small single-link regular file with the expected owner, and exits 0 only |
| 19 | # for a match. Not cmp: the agent owns /data and could swap the floor file |
| 20 | # between the shell's tests and cmp's open. |
| 21 | # Otherwise the current binary runs, and its own check refuses to restore a |
| 22 | # carlos.prev below the floor. No floor file: the choice is exactly as |
| 23 | # before. The line carries no percent sign and no dollar sign, both of which |
| 24 | # systemd expands before the shell sees them. |
| 25 | # |
| 26 | # The seeds floor (seeded-instances spec §3.3) adds the same test for its |
| 27 | # own files: once /data/.moves/seeds-floor.min-release is there and not |
| 28 | # empty, carlos.prev runs only when /opt/carlos/carlos.prev.seeds-allowed |
| 29 | # names that same floor, byte for byte. |
| 30 | # |
| 31 | # Deliberately no filesystem-sandboxing directive and no dedicated run |
| 32 | # account: it must write /opt/carlos and /etc/systemd/system, which only |
| 33 | # root can do. StartLimitIntervalSec=0 because the path unit can fire in |
| 34 | # bursts. TimeoutStartSec bounds a hung apply; OnFailure= makes a killed or |
| 35 | # timed-out apply recover without waiting for a touch. |
| 36 | [Unit] |
| 37 | Description=carlos platform self-update (root apply) |
| 38 | After=network-online.target |
| 39 | Wants=network-online.target |
| 40 | StartLimitIntervalSec=0 |
| 41 | OnFailure=carlos-system-recover.service |
| 42 | |
| 43 | [Service] |
| 44 | Type=oneshot |
| 45 | TimeoutStartSec=15min |
| 46 | EnvironmentFile=-/etc/carlos/host.env |
| 47 | ExecStart=/bin/sh -c 'if [ -x /opt/carlos/system/guard/carlos ] && { [ -e /var/lib/carlos-claims/authority/exec/census.json ] || [ -L /var/lib/carlos-claims/authority/exec/census.json ] || [ -e /opt/carlos/system/guard/moves ] || [ -L /opt/carlos/system/guard/moves ]; }; then exec /opt/carlos/system/guard/carlos system guard-exec apply; fi; if [ -f /opt/carlos/system/router/journal.json ] && [ -x /opt/carlos/carlos.prev ] && [ ! -L /data/.moves/floor.min-release ] && { [ ! -e /data/.moves/floor.min-release ] || [ -f /data/.moves/floor.min-release ]; } && { ! [ -s /data/.moves/floor.min-release ] || /opt/carlos/carlos.prev system floor-allows-prev /data/.moves/floor.min-release /opt/carlos/carlos.prev.allowed; } && [ ! -L /data/.moves/seeds-floor.min-release ] && { [ ! -e /data/.moves/seeds-floor.min-release ] || [ -f /data/.moves/seeds-floor.min-release ]; } && { ! [ -s /data/.moves/seeds-floor.min-release ] || /opt/carlos/carlos.prev system floor-allows-prev /data/.moves/seeds-floor.min-release /opt/carlos/carlos.prev.seeds-allowed; }; then exec /opt/carlos/carlos.prev system apply; fi; exec /opt/carlos/carlos system apply' |
| 48 | |
| 49 | # --- hardening (the carlos-stage.service set) --- |
| 50 | ProtectHome=yes |
| 51 | PrivateTmp=yes |
| 52 | PrivateDevices=yes |
| 53 | ProtectClock=yes |
| 54 | ProtectHostname=yes |
| 55 | ProtectKernelTunables=yes |
| 56 | ProtectKernelModules=yes |
| 57 | ProtectKernelLogs=yes |
| 58 | RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX |
| 59 | RestrictNamespaces=yes |
| 60 | RestrictRealtime=yes |
| 61 | LockPersonality=yes |
| 62 | MemoryDenyWriteExecute=yes |
| 63 | MemoryMax=256M |
| 64 | TasksMax=64 |
| 65 | |