# The tenant listener: systemd owns the unix socket at # /run/carlos/apps/.sock and passes the bound fd to carlos-app@ # (sd_listen_fds — the app sees LISTEN_FDS=1 and serves on fd 3). # # Why socket activation instead of the app binding a path itself: it is the # simplest design that gets the PERMISSIONS right. The DynamicUser tenant # owns the listening process, but the file is created by root and owned by # carlos with mode 0600 — so the edge (user carlos) can connect and no # tenant can reach another tenant's socket. This closes the loopback # cross-talk gap that TCP Addr routes have. The alternatives all leak: a # tenant-bound socket is owned by an ephemeral uid the edge can't connect # to; umask/chmod games make it world-connectable; a shared group doesn't # exist for DynamicUser without inventing static accounts. Here the tenant # never needs path access at all — the fd arrives already bound, and the # socket survives service restarts, so the edge's unix dial never even sees # the rebind window. # # It is also the wake-on-connect half of hibernation: with the service # stopped (the activator's idle timeout), a connection here starts it — # including while the agent itself is down. The activator's edge-mediated # wake (systemctl start + HTTP health wait) remains the primary path # because it is health-gated: a broken boot becomes a clean 503 at the # edge, not a connect parked forever in the listen backlog. # # Enable per tenant, alongside the route: # systemctl enable --now carlos-app@.socket # Apps that cannot accept an inherited listener yet keep a TCP Addr route # and NO socket unit — the documented fallback (with the cross-talk caveat # above, until the app learns LISTEN_FDS). [Unit] Description=CARLOS app %i socket [Socket] ListenStream=/run/carlos/apps/%i.sock SocketUser=carlos SocketGroup=carlos SocketMode=0600 [Install] WantedBy=sockets.target