| 1 | [Unit] |
| 2 | Description=lchat control daemon (local provisioning API) |
| 3 | After=network-online.target |
| 4 | Wants=network-online.target |
| 5 | |
| 6 | [Service] |
| 7 | # The router's env FIRST: controld executes add-instance, whose mesh route |
| 8 | # replication (fanOutMeshRoute) must see the box's mesh identity exactly as |
| 9 | # the router does (LCHAT_MESH_SELF/_TOKEN/_DOMAINS, the hibernate bucket + |
| 10 | # store credentials). Without this line the fan-out silently no-ops and any |
| 11 | # create HRW-placed on a peer box is born dark — the peer answers "unknown |
| 12 | # host" for it. control.env second, so control-specific values still win. |
| 13 | EnvironmentFile=-/data/router.env |
| 14 | # Optional overrides: LCHAT_MAX_INSTANCES / LCHAT_MIN_FREE_GB (capacity policy). |
| 15 | EnvironmentFile=-/data/control.env |
| 16 | ExecStart=/opt/lchat/lchat controld |
| 17 | Restart=always |
| 18 | RestartSec=1 |
| 19 | |
| 20 | # --- hardening (see docs/instance-router.md § Hardening) --- |
| 21 | # Runs as ROOT deliberately: this is the one process allowed to call systemctl |
| 22 | # and write the version store (/opt/lchat), which is why it serves a local unix |
| 23 | # socket only and is never routed publicly. NoNewPrivileges and the syscall |
| 24 | # filter are omitted for the systemctl child; the rest of the sandbox applies. |
| 25 | ProtectHome=yes |
| 26 | PrivateTmp=yes |
| 27 | PrivateDevices=yes |
| 28 | ProtectClock=yes |
| 29 | ProtectHostname=yes |
| 30 | ProtectKernelTunables=yes |
| 31 | ProtectKernelModules=yes |
| 32 | ProtectKernelLogs=yes |
| 33 | RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX |
| 34 | RestrictNamespaces=yes |
| 35 | RestrictRealtime=yes |
| 36 | LockPersonality=yes |
| 37 | MemoryDenyWriteExecute=yes |
| 38 | MemoryMax=256M |
| 39 | TasksMax=64 |
| 40 | |
| 41 | [Install] |
| 42 | WantedBy=multi-user.target |
| 43 | |