mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 15:37:03 +00:00
feat(system-tuning): new package for swap + LXC cgroup memory caps (closes #391)
Captures the gk2 RAM optimization applied by hand on 2026-05-27.
Before: 117 MB free, 0 swap, no cgroup caps → 95+ packages thrashing.
After: 1.1 GB free, 4 GB swap, 12 LXC containers throttled at 2x current
RSS via cgroup MemoryHigh (soft cap = throttle, not OOM-kill).
Package contents:
sbin/secubox-tuning-apply idempotent CLI; reads two TOML
configs and applies swap + cgroup
caps. Supports --dry-run, swap-only,
cgroup-only.
etc/secubox/tuning/config.toml swap path + size (default 4G) +
minimum_data_free_gb safety.
etc/secubox/tuning/lxc-memory-high.toml
12 container → MemoryHigh mapping
(mail=500M, matrix=400M, etc.) from
gk2 baseline.
debian/postinst runs secubox-tuning-apply with safe
defaults.
debian/prerm deliberately preserves swap and
drop-ins on uninstall (manual
cleanup documented inline).
Cgroup caps applied two ways:
1. Direct write to /sys/fs/cgroup/lxc.payload.<name>/memory.high for
immediate runtime effect on running containers (no restart).
2. Edit of /data/lxc/<name>/config or /var/lib/lxc/<name>/config to
add lxc.cgroup2.memory.high = <cap>, so lxc-autostart re-applies
the cap on every container restart.
Initial attempt used systemd drop-ins under
/etc/systemd/system/lxc@<name>.service.d/ — turned out to be ineffective
on boards where containers are started by lxc-autostart (not by
systemctl start lxc@<name>.service). Refactored to the direct-cgroup +
LXC-config approach during gk2 verification.
Verified on gk2: all 12 containers show non-"max" memory.high in cgroup
+ matching lxc.cgroup2.memory.high in their LXC config files.
Distinct from secubox-hardening (kernel sysctl + AppArmor + module
blacklist for security) — this package is performance tuning.
This commit is contained in:
parent
d20aacf8cf
commit
b54f9c4e96
19
packages/secubox-system-tuning/debian/changelog
Normal file
19
packages/secubox-system-tuning/debian/changelog
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
secubox-system-tuning (1.0.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Initial release — captures the gk2 RAM optimization applied by
|
||||
hand on 2026-05-27 (closes #391).
|
||||
* Creates /data/swapfile (default 4G) if /data has enough free
|
||||
space and no swap is currently active; persists in /etc/fstab.
|
||||
* Applies per-LXC-container cgroup MemoryHigh caps two ways:
|
||||
- immediate cgroup write to /sys/fs/cgroup/lxc.payload.<name>/
|
||||
memory.high for runtime effect, no restart needed;
|
||||
- persistent edit of /data/lxc/<name>/config (or /var/lib/lxc/
|
||||
<name>/config — falls back) adding
|
||||
lxc.cgroup2.memory.high = <cap> so the cap is re-applied on
|
||||
every lxc-start by lxc-autostart at boot.
|
||||
* Idempotent secubox-tuning-apply script — re-run after editing
|
||||
/etc/secubox/tuning/lxc-memory-high.toml to update caps.
|
||||
* Default cap profile from gk2 (mail/matrix/mitmproxy/nextcloud/
|
||||
lyrion/grafana/horde/authelia/mqtt/rustdesk/roundcube/zigbee).
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Wed, 27 May 2026 19:45:00 +0000
|
||||
28
packages/secubox-system-tuning/debian/control
Normal file
28
packages/secubox-system-tuning/debian/control
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Source: secubox-system-tuning
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Gerald KERMA <devel@cybermind.fr>
|
||||
Build-Depends: debhelper-compat (= 13)
|
||||
Standards-Version: 4.6.2
|
||||
|
||||
Package: secubox-system-tuning
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, systemd, util-linux, python3
|
||||
Recommends: lxc
|
||||
Description: SecuBox System Tuning — swap + cgroup MemoryHigh caps for LXC containers
|
||||
Per-board memory tuning for SecuBox: configures a swap file in /data
|
||||
(boards typically ship with 0 swap and exhaust real memory under LXC
|
||||
load), and applies per-LXC-container cgroup MemoryHigh soft caps
|
||||
to throttle (not OOM-kill) runaway containers.
|
||||
.
|
||||
Captures the by-hand fix applied on gk2 on 2026-05-27 (#391) so
|
||||
other boards inherit it automatically. Idempotent: re-running the
|
||||
tuning script after editing the TOML config only diffs the deltas.
|
||||
.
|
||||
Configuration lives at /etc/secubox/tuning/{config.toml,
|
||||
lxc-memory-high.toml}. Apply with secubox-tuning-apply or wait
|
||||
for the next package upgrade.
|
||||
.
|
||||
Distinct from secubox-hardening (kernel sysctl + AppArmor +
|
||||
module blacklist for *security*) — this package is *performance*
|
||||
tuning: swap and cgroup soft limits.
|
||||
14
packages/secubox-system-tuning/debian/postinst
Executable file
14
packages/secubox-system-tuning/debian/postinst
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ "$1" = "configure" ]; then
|
||||
# Apply tuning with safe defaults. Idempotent; if swap is already
|
||||
# active or caps are already applied, this is a no-op.
|
||||
if [ -x /usr/sbin/secubox-tuning-apply ]; then
|
||||
/usr/sbin/secubox-tuning-apply || \
|
||||
echo "secubox-system-tuning: apply failed (non-fatal, edit /etc/secubox/tuning/ and re-run /usr/sbin/secubox-tuning-apply)" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
#DEBHELPER#
|
||||
exit 0
|
||||
13
packages/secubox-system-tuning/debian/prerm
Executable file
13
packages/secubox-system-tuning/debian/prerm
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# On removal: deliberately leave /data/swapfile and the LXC drop-ins
|
||||
# in place. Removing them mid-board-life could trigger OOM-killer on
|
||||
# heavy LXC containers. Operator can remove manually:
|
||||
# swapoff /data/swapfile && rm /data/swapfile
|
||||
# rm -rf /etc/systemd/system/lxc@*.service.d/50-memory-high.conf
|
||||
# systemctl daemon-reload
|
||||
# <restart LXC containers to drop runtime caps>
|
||||
|
||||
#DEBHELPER#
|
||||
exit 0
|
||||
10
packages/secubox-system-tuning/debian/rules
Executable file
10
packages/secubox-system-tuning/debian/rules
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_install:
|
||||
install -d debian/secubox-system-tuning/usr/sbin
|
||||
install -m 755 sbin/secubox-tuning-apply debian/secubox-system-tuning/usr/sbin/
|
||||
install -d debian/secubox-system-tuning/etc/secubox/tuning
|
||||
install -m 644 etc/secubox/tuning/config.toml debian/secubox-system-tuning/etc/secubox/tuning/
|
||||
install -m 644 etc/secubox/tuning/lxc-memory-high.toml debian/secubox-system-tuning/etc/secubox/tuning/
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# /etc/secubox/tuning/config.toml — SecuBox memory tuning master config
|
||||
# Modify and re-run /usr/sbin/secubox-tuning-apply to re-apply.
|
||||
|
||||
[swap]
|
||||
# Path of the swap file. Lives on /data because boards typically have
|
||||
# the bulk of their storage there (mSSD), not on / (microSD).
|
||||
path = "/data/swapfile"
|
||||
|
||||
# Swap size. Use 'G' or 'M' suffix (passed to fallocate -l).
|
||||
# Default 4G is sized for 8 GB boards with heavy LXC load (gk2 baseline).
|
||||
size = "4G"
|
||||
|
||||
# Minimum free space on /data before secubox-tuning-apply will allocate
|
||||
# the swap file. Safety: avoid filling up /data on a board with limited
|
||||
# storage.
|
||||
minimum_data_free_gb = 8
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# /etc/secubox/tuning/lxc-memory-high.toml — per-LXC-container MemoryHigh caps.
|
||||
#
|
||||
# MemoryHigh is a SOFT cap: when the container exceeds it, the kernel
|
||||
# throttles allocations rather than OOM-killing (which would happen on
|
||||
# MemoryMax). Tune up if you see throttling in `journalctl -u lxc@<name>`
|
||||
# or if a container becomes unresponsive under load.
|
||||
#
|
||||
# Each entry generates /etc/systemd/system/lxc@<container>.service.d/
|
||||
# 50-memory-high.conf with [Service] MemoryHigh=<value>. Applied at
|
||||
# runtime by secubox-tuning-apply via `systemctl set-property` (no
|
||||
# container restart required for already-running containers).
|
||||
#
|
||||
# Defaults below are from gk2 baseline (2026-05-27, post-#388 deploy).
|
||||
# They are roughly 2× current observed RSS for each container.
|
||||
|
||||
[caps]
|
||||
mail = "500M" # postfix + dovecot + rspamd
|
||||
mitmproxy = "400M" # WAF inspector
|
||||
matrix = "400M" # synapse
|
||||
nextcloud = "600M" # NC + bundled mariadb
|
||||
lyrion = "500M" # Logitech Media Server
|
||||
grafana = "400M" # grafana-server
|
||||
horde = "300M" # horde webmail
|
||||
authelia = "200M" # SSO IdP
|
||||
mqtt = "150M" # mosquitto
|
||||
rustdesk = "200M" # rustdesk relay
|
||||
roundcube = "200M" # roundcube webmail (separate from horde)
|
||||
zigbee = "200M" # zigbee2mqtt
|
||||
202
packages/secubox-system-tuning/sbin/secubox-tuning-apply
Executable file
202
packages/secubox-system-tuning/sbin/secubox-tuning-apply
Executable file
|
|
@ -0,0 +1,202 @@
|
|||
#!/usr/bin/env bash
|
||||
# /usr/sbin/secubox-tuning-apply — apply swap + cgroup MemoryHigh tuning.
|
||||
#
|
||||
# Idempotent. Reads config from /etc/secubox/tuning/.
|
||||
#
|
||||
# Usage:
|
||||
# secubox-tuning-apply # apply both swap and cgroup caps
|
||||
# secubox-tuning-apply swap # swap only
|
||||
# secubox-tuning-apply cgroup # cgroup caps only
|
||||
# secubox-tuning-apply --dry-run # show what would be done
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="/etc/secubox/tuning/config.toml"
|
||||
LXC_CAPS_FILE="/etc/secubox/tuning/lxc-memory-high.toml"
|
||||
|
||||
CYAN='\033[0;36m'
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${CYAN}[tuning]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[ ok ]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[ warn ]${NC} $*"; }
|
||||
err() { echo -e "${RED}[fail ]${NC} $*" >&2; }
|
||||
|
||||
DRY_RUN=0
|
||||
DO_SWAP=1
|
||||
DO_CGROUP=1
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
swap) DO_CGROUP=0 ;;
|
||||
cgroup) DO_SWAP=0 ;;
|
||||
-h|--help)
|
||||
sed -n '1,/^set -/p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||
exit 0
|
||||
;;
|
||||
*) err "unknown arg: $arg"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
run() {
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
log "DRY-RUN: $*"
|
||||
else
|
||||
eval "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Minimal TOML reader using python3 (avoids depending on toml-cli).
|
||||
toml_get() {
|
||||
local file="$1" key="$2" default="${3:-}"
|
||||
python3 -c "
|
||||
import sys
|
||||
try:
|
||||
import tomllib
|
||||
except ImportError:
|
||||
import tomli as tomllib
|
||||
try:
|
||||
d = tomllib.loads(open('$file').read())
|
||||
for k in '$key'.split('.'):
|
||||
d = d[k]
|
||||
print(d)
|
||||
except (KeyError, FileNotFoundError):
|
||||
print('$default')
|
||||
" 2>/dev/null || echo "$default"
|
||||
}
|
||||
|
||||
toml_table_keys() {
|
||||
local file="$1" table="$2"
|
||||
python3 -c "
|
||||
import sys
|
||||
try:
|
||||
import tomllib
|
||||
except ImportError:
|
||||
import tomli as tomllib
|
||||
d = tomllib.loads(open('$file').read())
|
||||
for k in d.get('$table', {}):
|
||||
print(k)
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
apply_swap() {
|
||||
log "swap phase"
|
||||
local swap_path size min_free_gb
|
||||
swap_path=$(toml_get "$CONFIG_FILE" swap.path /data/swapfile)
|
||||
size=$(toml_get "$CONFIG_FILE" swap.size 4G)
|
||||
min_free_gb=$(toml_get "$CONFIG_FILE" swap.minimum_data_free_gb 8)
|
||||
|
||||
if swapon --show=NAME --noheadings | grep -q '.'; then
|
||||
ok "swap already active: $(swapon --show=NAME --noheadings | tr '\n' ' ')"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -d "$(dirname "$swap_path")" ]]; then
|
||||
warn "swap parent dir $(dirname "$swap_path") missing, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local free_gb
|
||||
free_gb=$(df --output=avail -BG "$(dirname "$swap_path")" | tail -1 | tr -d 'G ')
|
||||
if [[ "$free_gb" -lt "$min_free_gb" ]]; then
|
||||
warn "/data free space ${free_gb}G < required ${min_free_gb}G; skipping swap creation"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "$swap_path" ]]; then
|
||||
log "$swap_path exists, activating"
|
||||
run "swapon $swap_path"
|
||||
else
|
||||
log "creating $swap_path ($size)"
|
||||
run "fallocate -l $size $swap_path"
|
||||
run "chmod 600 $swap_path"
|
||||
run "mkswap $swap_path"
|
||||
run "swapon $swap_path"
|
||||
fi
|
||||
|
||||
if ! grep -q "^$swap_path " /etc/fstab; then
|
||||
log "persisting in /etc/fstab"
|
||||
run "echo '$swap_path none swap sw 0 0' >> /etc/fstab"
|
||||
fi
|
||||
ok "swap: $(swapon --show)"
|
||||
}
|
||||
|
||||
apply_cgroup() {
|
||||
log "cgroup MemoryHigh phase"
|
||||
if [[ ! -f "$LXC_CAPS_FILE" ]]; then
|
||||
warn "$LXC_CAPS_FILE not found, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local containers
|
||||
containers=$(toml_table_keys "$LXC_CAPS_FILE" caps)
|
||||
if [[ -z "$containers" ]]; then
|
||||
warn "no caps defined in $LXC_CAPS_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# On SecuBox boards LXC containers are started by lxc-autostart at boot,
|
||||
# NOT by systemctl start lxc@<name>.service. So drop-ins under
|
||||
# /etc/systemd/system/lxc@<name>.service.d/ are ineffective. Apply the
|
||||
# cap two ways instead:
|
||||
# 1. Direct write to the container's existing cgroup
|
||||
# (/sys/fs/cgroup/lxc.payload.<name>/memory.high) for immediate
|
||||
# effect, no restart.
|
||||
# 2. Add lxc.cgroup2.memory.high=<cap> to the container's LXC config
|
||||
# (/var/lib/lxc/<name>/config OR /data/lxc/<name>/config — try
|
||||
# both, take the first that exists) so the cap is reapplied on
|
||||
# every lxc-start.
|
||||
|
||||
local applied=0 persisted=0 missed=0
|
||||
for ctn in $containers; do
|
||||
local cap cgrp lxc_conf
|
||||
cap=$(toml_get "$LXC_CAPS_FILE" "caps.$ctn")
|
||||
|
||||
# (1) immediate cgroup write
|
||||
cgrp="/sys/fs/cgroup/lxc.payload.${ctn}"
|
||||
if [[ -d "$cgrp" ]]; then
|
||||
run "echo $cap > $cgrp/memory.high"
|
||||
applied=$((applied+1))
|
||||
printf " %-12s cgroup memory.high=%s (runtime)" "$ctn" "$cap"
|
||||
else
|
||||
missed=$((missed+1))
|
||||
printf " %-12s container not running (no cgroup)" "$ctn"
|
||||
fi
|
||||
|
||||
# (2) persistent LXC config edit. Look in both standard and /data paths.
|
||||
lxc_conf=""
|
||||
for path in "/data/lxc/${ctn}/config" "/var/lib/lxc/${ctn}/config"; do
|
||||
if [[ -f "$path" ]]; then
|
||||
lxc_conf="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n "$lxc_conf" ]]; then
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
printf " + DRY-RUN: would set memory.high=%s in %s\n" "$cap" "$lxc_conf"
|
||||
else
|
||||
# Replace existing memory.high line if present, otherwise append.
|
||||
if grep -q "^lxc\.cgroup2\.memory\.high\s*=" "$lxc_conf"; then
|
||||
sed -i "s|^lxc\.cgroup2\.memory\.high\s*=.*|lxc.cgroup2.memory.high = ${cap}|" "$lxc_conf"
|
||||
else
|
||||
echo "lxc.cgroup2.memory.high = ${cap}" >> "$lxc_conf"
|
||||
fi
|
||||
printf " + persisted in %s\n" "$lxc_conf"
|
||||
persisted=$((persisted+1))
|
||||
fi
|
||||
else
|
||||
printf "\n"
|
||||
fi
|
||||
done
|
||||
|
||||
ok "cgroup caps: $applied runtime + $persisted persisted + $missed missed"
|
||||
}
|
||||
|
||||
log "secubox-tuning-apply (dry_run=$DRY_RUN, swap=$DO_SWAP, cgroup=$DO_CGROUP)"
|
||||
[[ $DO_SWAP -eq 1 ]] && apply_swap
|
||||
[[ $DO_CGROUP -eq 1 ]] && apply_cgroup
|
||||
ok "done"
|
||||
Loading…
Reference in New Issue
Block a user