# LLeMbas system service template. # # install.sh substitutes __PREFIX__ and __SERVICE_USER__ and writes the result # to /etc/systemd/system/lembas.service. Edit this file, not the installed copy. # # A system unit, not a user unit, so it survives logout and comes up at boot # without anyone signing in. [Unit] Description=LLeMbas - web UI for language models After=network-online.target Wants=network-online.target # The prefix is usually a bind mount; the venv and database live there, so # starting before it is mounted would create an empty database in its place. RequiresMountsFor=__PREFIX__ [Service] Type=simple User=__SERVICE_USER__ Group=__SERVICE_USER__ WorkingDirectory=__PREFIX__/app EnvironmentFile=__PREFIX__/lembas.env ExecStart=__PREFIX__/venv/bin/lembas serve Restart=on-failure RestartSec=5 # The bind address comes from LEMBAS_HOST in the environment file, which the # installer sets to 127.0.0.1: reachable through nginx, never directly. # --- Hardening ------------------------------------------------------------- # Moderate rather than maximal. The agentic features need to run commands, and # a lockdown that has to be torn out again is worse than one that was never # applied. # # ProtectKernelTunables is deliberately ABSENT, and putting it back breaks # agent chats outright. It bind-mounts /proc/sys read-only, which leaves a # locked submount under /proc; the kernel then refuses `mount -t proc` inside a # user namespace, and bubblewrap fails with # # bwrap: Can't mount proc on /newroot/proc: Operation not permitted # # The tempting workaround is worse than the disease: binding the host /proc # into the sandbox would expose /proc//environ of this process, and this # unit reads LEMBAS_SECRET_KEY out of an EnvironmentFile. The setting only # guards against a *root* write to /proc/sys, and this service is unprivileged # with NoNewPrivileges, so little is given up. # # NoNewPrivileges is fine alongside bubblewrap because bwrap is not setuid here # -- it uses an unprivileged user namespace with a single-uid map, which needs # no /etc/subuid entry for the service account. NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=full ProtectControlGroups=yes RestrictSUIDSGID=yes ReadWritePaths=__PREFIX__ LimitNOFILE=65535 # A sandbox gets no cgroup of its own, and RLIMIT_NPROC is per *uid* -- the # same uid as this service. Bounding the whole unit is what stops a runaway # command in an agent chat from taking the server down with it. TasksMax=2048 MemoryMax=8G [Install] WantedBy=multi-user.target