[Unit] Description=lchat instance %i After=network-online.target Wants=network-online.target [Service] # add-instance writes /data/.env (0600) with LCHAT_ADMIN_TOKEN + per-instance # config. The leading '-' makes it optional so a self-provisioning instance still boots. EnvironmentFile=-/data/%i.env # Execs a per-instance symlink (/opt/lchat/live/ → /opt/lchat/versions// # lchat) rather than one shared binary, so each instance runs its own version and a # build can be canaried on one instance before it's promoted. add-instance / # init-versions create the link; `lchat deploy --version` repoints it. See # docs/instance-router.md § Versioned deploys. ExecStart=/opt/lchat/live/%i --socket /run/lchat/%i.sock --db /data/%i.db Restart=always RestartSec=1 # --- hardening (see docs/instance-router.md § Hardening) --- User=lchat Group=lchat # Binds a unix socket only — no capabilities needed. CapabilityBoundingSet= NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/data /run/lchat PrivateTmp=yes PrivateDevices=yes ProtectClock=yes ProtectHostname=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectControlGroups=yes ProtectProc=invisible RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes LockPersonality=yes MemoryDenyWriteExecute=yes SystemCallFilter=@system-service SystemCallErrorNumber=EPERM SystemCallArchitectures=native MemoryMax=512M TasksMax=256 [Install] WantedBy=multi-user.target