[OCaml] High Intensity Training Online
build Install hardened systemd unit from repo
Keep hito.service in the repo as the source of truth. setup.sh install and deploy now install it when it changed, after systemd-analyze verify and with a timestamped backup of the old unit. The old point_unit only sed-edited a hand-made unit during install. The unit narrows ReadWritePaths to /srv/hito/shared, so the service cannot rewrite releases or the current symlink. It also adds capability, syscall, and namespace hardening and UMask=0077, and groups the environment lines to show EnvironmentFile precedence.
Changed files
deploy/hito.service
@@ -0,0 +1,52 @@
1
Added:
# Installed by deploy/setup.sh to /etc/systemd/system/hito.service.
2
Added:
# Edit this file, not the installed copy: install and deploy overwrite it.
3
Added:
4
Added:
[Unit]
5
Added:
Description=hito — High Intensity Trainer Online
6
Added:
After=network.target
7
Added:
8
Added:
[Service]
9
Added:
Type=simple
10
Added:
User=git
11
Added:
Group=git
12
Added:
WorkingDirectory=/srv/hito/current
13
Added:
ExecStart=/srv/hito/current/_build/default/bin/main.exe
14
Added:
Restart=on-failure
15
Added:
RestartSec=5
16
Added:
17
Added:
# EnvironmentFile= values override Environment= values, whatever the order.
18
Added:
Environment=HITO_DB=sqlite3:/srv/hito/shared/hito.sqlite
19
Added:
Environment=HITO_PORT=8080
20
Added:
# HITO_VERSION, rewritten by every deploy.
21
Added:
EnvironmentFile=/srv/hito/shared/deploy.env
22
Added:
# Host secrets (HITO_SECRET and so on). Keep it root:root 0600.
23
Added:
EnvironmentFile=/etc/hito/hito.env
24
Added:
25
Added:
# Hardening. Only the database directory is writable.
26
Added:
UMask=0077
27
Added:
NoNewPrivileges=yes
28
Added:
ProtectSystem=strict
29
Added:
ProtectHome=yes
30
Added:
ReadWritePaths=/srv/hito/shared
31
Added:
ReadOnlyPaths=/srv/git
32
Added:
PrivateTmp=yes
33
Added:
PrivateDevices=yes
34
Added:
CapabilityBoundingSet=
35
Added:
AmbientCapabilities=
36
Added:
ProtectKernelTunables=yes
37
Added:
ProtectKernelModules=yes
38
Added:
ProtectKernelLogs=yes
39
Added:
ProtectControlGroups=yes
40
Added:
ProtectClock=yes
41
Added:
ProtectHostname=yes
42
Added:
RestrictSUIDSGID=yes
43
Added:
RestrictNamespaces=yes
44
Added:
RestrictRealtime=yes
45
Added:
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
46
Added:
LockPersonality=yes
47
Added:
MemoryDenyWriteExecute=yes
48
Added:
SystemCallArchitectures=native
49
Added:
SystemCallFilter=@system-service
50
Added:
51
Added:
[Install]
52
Added:
WantedBy=multi-user.target
deploy/setup.sh
@@ -5,11 +5,12 @@
5
5
# sudo bash setup.sh install One-time host setup: take git ownership of
6
6
# /srv/hito, build the shared switch as git,
7
7
# install the hook, the scoped sudoers rule, and
8
Removed:
# the systemd unit (User=git) pointed at current/.
9
Removed:
# Idempotent. Creates no release; the first push
10
Removed:
# (or 'deploy') does.
11
Removed:
# sudo bash setup.sh deploy Build master as a fresh release on the shared
12
Removed:
# switch and flip current. Health-checks and
8
Added:
# the systemd unit from hito.service beside this
9
Added:
# script. Idempotent. Creates no release; the
10
Added:
# first push (or 'deploy') does.
11
Added:
# sudo bash setup.sh deploy Reinstall hito.service if it changed, build
12
Added:
# master as a fresh release on the shared
13
Added:
# switch, and flip current. Health-checks and
13
14
# rolls back on failure. Use to go live without a
14
15
# push, or right after 'install'.
15
16
# sudo bash setup.sh reset Wipe /srv/hito to the clean layout, preserving
@@ -90,20 +91,22 @@
90
91
rm -f /usr/local/libexec/hito-deploy
91
92
}
92
93
93
Removed:
point_unit() {
94
Removed:
cp -a "${UNIT}" "${UNIT}.bak.$(date +%s)"
95
Removed:
sed -i \
96
Removed:
-e 's#^ExecStart=.*#ExecStart=/srv/hito/current/_build/default/bin/main.exe#' \
97
Removed:
-e 's#^WorkingDirectory=.*#WorkingDirectory=/srv/hito/current#' \
98
Removed:
-e 's#^Environment=HITO_DB=.*#Environment=HITO_DB=sqlite3:/srv/hito/shared/hito.sqlite#' \
99
Removed:
-e "s#^User=.*#User=${SVC_USER}#" \
100
Removed:
-e "s#^Group=.*#Group=${SVC_GROUP}#" \
101
Removed:
"${UNIT}"
102
Removed:
if ! grep -q '^EnvironmentFile=/srv/hito/shared/deploy.env$' "${UNIT}"; then
103
Removed:
sed -i '/^Environment=HITO_DB=/a EnvironmentFile=/srv/hito/shared/deploy.env' "${UNIT}"
94
Added:
# Install the unit shipped beside this script. The repo copy is the source of
95
Added:
# truth. A changed installed copy is backed up, then replaced.
96
Added:
install_unit() {
97
Added:
local src="${HERE}/hito.service"
98
Added:
if cmp -s "${src}" "${UNIT}"; then
99
Added:
echo " unit unchanged"
100
Added:
return 0
104
101
fi
102
Added:
systemd-analyze verify "${src}"
103
Added:
if [[ -f "${UNIT}" ]]; then
104
Added:
cp -a "${UNIT}" "${UNIT}.bak.$(date +%s)"
105
Added:
fi
106
Added:
install -o root -g root -m 0644 "${src}" "${UNIT}"
105
107
systemctl daemon-reload
106
Removed:
echo " unit runs as ${SVC_USER} and points at current/ (backup kept alongside)"
108
Added:
systemctl enable hito >/dev/null 2>&1
109
Added:
echo " installed ${UNIT} (previous copy kept alongside)"
107
110
}
108
111
109
112
health_ok() {
@@ -207,7 +210,7 @@
207
210
echo "== shared opam switch =="; ensure_switch
208
211
echo "== post-receive hook =="; install_hook
209
212
echo "== scoped sudoers =="; install_sudoers
210
Removed:
echo "== systemd unit =="; point_unit
213
Added:
echo "== systemd unit =="; install_unit
211
214
echo
212
215
echo "Done. Run 'sudo bash setup.sh deploy' to build the first release,"
213
216
echo "or push master to trigger the hook."
@@ -218,6 +221,7 @@
218
221
echo "FATAL: no shared switch; run 'install' first" >&2
219
222
exit 1
220
223
fi
224
Added:
echo "== systemd unit =="; install_unit
221
225
echo "== deploy master =="; build_and_flip
222
226
}
223
227