| 14 |
14 |
|
# bin_names[0]). Defaults to "makenotwork".
|
| 15 |
15 |
|
# SERVICE_NAME — systemd unit name. Defaults to "makenotwork.service".
|
| 16 |
16 |
|
# SERVICE_USER — runtime user for the binary. Defaults to "deploy".
|
|
17 |
+ |
# GIT_REPOS_PATH — where the server keeps bare git repositories. Defaults
|
|
18 |
+ |
# to $STATE_DIR/git, which is what prod and testnot both
|
|
19 |
+ |
# run. It must match GIT_REPOS_PATH in the env file: the
|
|
20 |
+ |
# unit's sandbox makes everything else read-only, and the
|
|
21 |
+ |
# binary's own default is /opt/git, so a node that omits
|
|
22 |
+ |
# it from the env file writes somewhere the sandbox
|
|
23 |
+ |
# forbids.
|
| 17 |
24 |
|
# ENABLE_FIREWALL — "1" to set up UFW (22/80/443). Defaults to "1".
|
| 18 |
25 |
|
# INSTALL_CADDY — "1" to apt-install caddy (config is operator's job).
|
| 19 |
26 |
|
# Defaults to "1".
|
| 46 |
53 |
|
ETC_DIR="${ETC_DIR:-/etc/mnw}"
|
| 47 |
54 |
|
ENV_FILE="${ENV_FILE:-$ETC_DIR/makenotwork.env}"
|
| 48 |
55 |
|
STATE_DIR="${STATE_DIR:-/var/lib/mnw}"
|
|
56 |
+ |
GIT_REPOS_PATH="${GIT_REPOS_PATH:-$STATE_DIR/git}"
|
| 49 |
57 |
|
BIN_NAME="${BIN_NAME:-makenotwork}"
|
| 50 |
58 |
|
SERVICE_NAME="${SERVICE_NAME:-makenotwork.service}"
|
| 51 |
59 |
|
SERVICE_USER="${SERVICE_USER:-deploy}"
|
| 130 |
138 |
|
fi
|
| 131 |
139 |
|
fi
|
| 132 |
140 |
|
|
|
141 |
+ |
# Bare git repositories. On a host that runs git SSH, two accounts write here:
|
|
142 |
+ |
# the web app creates the owner directory and the bare repo, and `git push`
|
|
143 |
+ |
# writes objects as the git user. Group-writable + setgid so whichever of them
|
|
144 |
+ |
# creates a directory, the other can still write inside it. Measured
|
|
145 |
+ |
# 2026-08-25: prod has this directory 0755 git:git, so the service cannot
|
|
146 |
+ |
# create a new owner directory at all and a creator's first repository fails
|
|
147 |
+ |
# there today — unrelated to the sandbox, which is why it is fixed here.
|
|
148 |
+ |
if getent passwd git >/dev/null; then
|
|
149 |
+ |
install -d -o "$SERVICE_USER" -g git -m 2775 "$GIT_REPOS_PATH"
|
|
150 |
+ |
else
|
|
151 |
+ |
install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0755 "$GIT_REPOS_PATH"
|
|
152 |
+ |
fi
|
|
153 |
+ |
|
| 133 |
154 |
|
log "6/8 sudoers (systemctl on $SERVICE_NAME for $SERVICE_USER)"
|
| 134 |
155 |
|
cat > "/etc/sudoers.d/${SERVICE_USER}-mnw" <<EOF
|
| 135 |
156 |
|
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl reload-or-restart $SERVICE_NAME, /bin/systemctl restart $SERVICE_NAME, /bin/systemctl status $SERVICE_NAME
|
| 155 |
176 |
|
EnvironmentFile=$ENV_FILE
|
| 156 |
177 |
|
# Runtime state (backups, spool, etc.) on FHS path; never inside the release
|
| 157 |
178 |
|
# dir or the deploy will erase it.
|
| 158 |
|
- |
ReadWritePaths=$STATE_DIR
|
|
179 |
+ |
ReadWritePaths=$STATE_DIR$(
|
|
180 |
+ |
# Bare repositories are normally under STATE_DIR and covered by the line
|
|
181 |
+ |
# above. Emit a second path only when the operator has put them elsewhere,
|
|
182 |
+ |
# so a sandbox that lists only STATE_DIR cannot silently break repository
|
|
183 |
+ |
# creation.
|
|
184 |
+ |
case "$GIT_REPOS_PATH/" in
|
|
185 |
+ |
"$STATE_DIR"/*) ;;
|
|
186 |
+ |
*) printf '\nReadWritePaths=%s' "$GIT_REPOS_PATH" ;;
|
|
187 |
+ |
esac
|
|
188 |
+ |
)
|
| 159 |
189 |
|
Restart=on-failure
|
| 160 |
190 |
|
RestartSec=30
|
| 161 |
191 |
|
# Exit 2 = migration failure (MNW server convention). Don't restart;
|
| 168 |
198 |
|
# rather than \$STATE_DIR and the two names are not interchangeable.
|
| 169 |
199 |
|
StateDirectory=makenotwork/scan-spool
|
| 170 |
200 |
|
StateDirectoryMode=0700
|
| 171 |
|
- |
# The ceilings server/docs/troubleshooting.md documents.
|
|
201 |
+ |
# The ceilings server/docs/troubleshooting.md documents. Both are measured
|
|
202 |
+ |
# rather than chosen: prod's own cgroup reported 392M anonymous (unreclaimable)
|
|
203 |
+ |
# with a 403M peak on 2026-08-25, so the 512M this template used to write would
|
|
204 |
+ |
# have left ~110M of headroom and been an OOM kill on the first content export,
|
|
205 |
+ |
# which holds one file of up to 500M in memory. MemoryHigh throttles and
|
|
206 |
+ |
# reclaims before MemoryMax kills. The soft NOFILE the service actually gets
|
|
207 |
+ |
# without this line is 1024, not the 524288 that \`systemctl show\` reports
|
|
208 |
+ |
# (that is the hard limit); the server had 18 descriptors open when measured.
|
| 172 |
209 |
|
LimitNOFILE=65535
|
| 173 |
|
- |
MemoryMax=512M
|
|
210 |
+ |
MemoryHigh=1G
|
|
211 |
+ |
MemoryMax=2G
|
|
212 |
+ |
# The sandbox. Verified 2026-08-25 by running a probe under exactly these
|
|
213 |
+ |
# options as the service user, on prod and on testnot: bare-repo creation, the
|
|
214 |
+ |
# scan spool, an export's private /tmp, clamd's unix socket, postgres over both
|
|
215 |
+ |
# TCP and its unix socket, and the yara rules all still work, while /opt, /etc
|
|
216 |
+ |
# and the rest of /var are read-only. ProtectSystem=strict is what makes the
|
|
217 |
+ |
# ReadWritePaths above load-bearing rather than decorative.
|
|
218 |
+ |
NoNewPrivileges=yes
|
|
219 |
+ |
ProtectSystem=strict
|
|
220 |
+ |
ProtectHome=yes
|
|
221 |
+ |
PrivateTmp=yes
|
|
222 |
+ |
PrivateDevices=yes
|
|
223 |
+ |
ProtectProc=invisible
|
|
224 |
+ |
ProtectKernelTunables=yes
|
|
225 |
+ |
ProtectKernelModules=yes
|
|
226 |
+ |
ProtectKernelLogs=yes
|
|
227 |
+ |
ProtectControlGroups=yes
|
|
228 |
+ |
ProtectClock=yes
|
|
229 |
+ |
ProtectHostname=yes
|
|
230 |
+ |
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
231 |
+ |
RestrictNamespaces=yes
|
|
232 |
+ |
RestrictRealtime=yes
|
|
233 |
+ |
RestrictSUIDSGID=yes
|
|
234 |
+ |
LockPersonality=yes
|
|
235 |
+ |
MemoryDenyWriteExecute=yes
|
|
236 |
+ |
SystemCallArchitectures=native
|
|
237 |
+ |
SystemCallFilter=@system-service
|
|
238 |
+ |
SystemCallErrorNumber=EPERM
|
| 174 |
239 |
|
StandardOutput=journal
|
| 175 |
240 |
|
StandardError=journal
|
| 176 |
241 |
|
SyslogIdentifier=$BIN_NAME
|
| 178 |
243 |
|
[Install]
|
| 179 |
244 |
|
WantedBy=multi-user.target
|
| 180 |
245 |
|
EOF
|
| 181 |
|
- |
# Deliberately absent: the systemd sandbox (ProtectSystem, NoNewPrivileges,
|
| 182 |
|
- |
# ProtectHome, the Restrict* set). Turning it on needs a measured
|
| 183 |
|
- |
# ReadWritePaths, because the server writes bare git repositories under
|
| 184 |
|
- |
# GIT_REPOS_PATH (/opt/git by default) as well as STATE_DIR and the scan
|
| 185 |
|
- |
# spool. A sandbox that lists only STATE_DIR breaks repository creation.
|
| 186 |
|
- |
# GoingsOn mnw-server task ad427fb3.
|
| 187 |
246 |
|
systemctl daemon-reload
|
| 188 |
247 |
|
systemctl enable "$SERVICE_NAME" >/dev/null 2>&1 || true
|
| 189 |
248 |
|
|