carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# The single-box role: `carlos agent` = edge (TLS terminator + host router) +
2# hibernation activator + hourly release adoption in one process. Ported from
3# Eleven's deploy/systemd/lchat-router.service, renamed, same hardening.
4# Hibernating instances run as THIS unit's children (the activator); systemd-
5# managed instances get their own units.
6[Unit]
7Description=carlos edge (TLS terminator, host router, activator, release adoption)
8# The three nosymfollow mounts of the isolated-exec design (§3.2) are hard
9# requirements: an edge that starts without them would start unprotected,
10# so it does not start at all.
11Requires=data-instances.mount run-carlos-instances.mount run-carlos-logs.mount
12After=network-online.target data-instances.mount run-carlos-instances.mount run-carlos-logs.mount
13Wants=network-online.target
14
15[Service]
16# Optional host-wide config (0600, root-owned): CARLOS_HIBERNATE_*,
17# CARLOS_DEPLOYMENT_BUCKET/_DIR, CARLOS_RELEASE_PUBKEY, CARLOS_RESERVED_SLUGS...
18EnvironmentFile=-/etc/carlos/host.env
19ExecStart=/opt/carlos/carlos agent
20# `carlos add` writes are picked up by the registry fingerprint watcher within
21# seconds; SIGHUP is the operator's explicit "reload now".
22ExecReload=/bin/kill -HUP $MAINPID
23Restart=always
24RestartSec=1
25
26# --- hardening (ported from lchat-router.service; see Eleven's
27# docs/instance-router.md § Hardening) ---
28User=carlos
29Group=carlos
30# The edge is the ONLY unit that binds privileged ports (:80 + :443); this cap
31# lets it do so as a non-root user. Instances bind unix sockets only.
32AmbientCapabilities=CAP_NET_BIND_SERVICE
33CapabilityBoundingSet=CAP_NET_BIND_SERVICE
34NoNewPrivileges=yes
35ProtectSystem=strict
36ProtectHome=yes
37ReadWritePaths=/data /run/carlos
38PrivateTmp=yes
39PrivateDevices=yes
40ProtectClock=yes
41ProtectHostname=yes
42ProtectKernelTunables=yes
43ProtectKernelModules=yes
44ProtectKernelLogs=yes
45# ProtectControlGroups is deliberately ABSENT: it would mount /sys/fs/cgroup
46# read-only, and Delegate= (below) needs this unit's own subtree writable.
47# The hardening that line bought is retained by ownership instead — plain
48# DAC keeps everything OUTSIDE the chowned subtree read-only to this
49# unprivileged user.
50ProtectProc=invisible
51RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
52RestrictNamespaces=yes
53RestrictRealtime=yes
54# No RestrictSUIDSGID=: systemd cannot read the mode inside openat2's
55# open_how, so it refuses openat2 outright (ENOSYS), and the agent's every
56# open under an instance directory, and every rename of the layout pass,
57# is an openat2 (isolated-exec design R15; the templates lost the line
58# first and this unit was found still carrying it, #126). R15's reasoning
59# for the templates carries here, and the one capability this unit holds
60# does not change it: a set-id file is a threat through CAP_CHOWN,
61# CAP_FSETID or CAP_SETUID, none of which is in the bounding set, so
62# anything the agent could mark set-id is owned by carlos and gains carlos
63# only to a process that is not already carlos and is not under
64# no_new_privs. The agent and its flat children run under
65# NoNewPrivileges=; the nested templates hold no capabilities and see only
66# their own directories; /run is nosuid, and the instance trees under
67# /data are mounted nosuid. The agent passes no set-id bit anywhere
68# (instancefs strips modes to their permission bits), so what is left is a
69# compromised agent handing its own uid to a lower-privileged unit, which
70# is no gain for an attacker that already has it. The agent checks openat2
71# in its own unit at start (instancefs.SelfCheck) and refuses nested wakes
72# and the layout pass while the call is blocked, so this line coming back
73# is loud, not a half-migrated host.
74LockPersonality=yes
75# Pure Go — the binary never maps writable+executable memory.
76MemoryDenyWriteExecute=yes
77SystemCallFilter=@system-service
78SystemCallErrorNumber=EPERM
79SystemCallArchitectures=native
80# The outer wall (memory-pressure spec 2026-08-28 §2.2) used to be two
81# MemoryHigh/MemoryMax lines here, bounding the agent plus its exec
82# children. Instances are sibling units now (isolated-exec design §4.3), so
83# the wall is carlos.slice's, which holds this unit and every instance and
84# bounds exactly what it bounded before. The oom_score_adj story is
85# unchanged: tenants carry OOMScoreAdjust=500 in their template.
86Slice=carlos.slice
87# systemd's default OOMPolicy=stop STOPS the whole service when the kernel
88# OOM-kills ANY process in the unit's cgroup tree (memory.events is
89# hierarchical, delegation or not). Restart=always would bring the edge
90# back a second later and cycle every awake instance on the box. With
91# continue, one tenant's kill is one tenant's kill; the activator recycles
92# it on its next request.
93OOMPolicy=continue
94TasksMax=1024
95# Exec-backed instances run as this unit's children. Delegation hands the
96# unit its own cgroup subtree — chowned to User=carlos, unmanaged by systemd
97# below this point — so the agent can give each child its own cgroup for
98# per-instance accounting (docs/superpowers/specs/
99# 2026-08-07-exec-child-accounting-design.md). The subtree still dies with
100# the service: restart semantics are unchanged.
101Delegate=cpu memory io pids
102
103[Install]
104WantedBy=multi-user.target
105