feat(deploy): left4me-apply-cake helper with apply/clear modes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d783449d05
commit
878639147a
2 changed files with 80 additions and 0 deletions
47
deploy/files/usr/local/libexec/left4me/left4me-apply-cake
Executable file
47
deploy/files/usr/local/libexec/left4me/left4me-apply-cake
Executable file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# left4me — apply or clear CAKE egress shaper on the configured uplink.
|
||||||
|
# Driven by left4me-cake.service. See spec
|
||||||
|
# docs/superpowers/specs/2026-05-10-l4d2-network-shaping-design.md.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mode=${1:-apply}
|
||||||
|
|
||||||
|
if [ -r /etc/left4me/cake.env ]; then
|
||||||
|
. /etc/left4me/cake.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_iface() {
|
||||||
|
if [ -n "${LEFT4ME_UPLINK_IFACE:-}" ]; then
|
||||||
|
printf '%s' "$LEFT4ME_UPLINK_IFACE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
ip -4 route show default | awk '/default/ {print $5; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
apply)
|
||||||
|
if [ -z "${LEFT4ME_UPLINK_MBIT:-}" ]; then
|
||||||
|
echo "left4me-cake: LEFT4ME_UPLINK_MBIT unset; skipping shaper" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
iface=$(resolve_iface)
|
||||||
|
if [ -z "$iface" ]; then
|
||||||
|
echo "left4me-cake: cannot determine egress iface; skipping" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exec tc qdisc replace dev "$iface" root cake \
|
||||||
|
bandwidth "${LEFT4ME_UPLINK_MBIT}mbit" \
|
||||||
|
internet diffserv4 dual-dsthost
|
||||||
|
;;
|
||||||
|
clear)
|
||||||
|
iface=$(resolve_iface)
|
||||||
|
if [ -z "$iface" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
tc qdisc del dev "$iface" root 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 [apply|clear]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -20,6 +20,7 @@ SYSTEMCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-systemctl"
|
||||||
JOURNALCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-journalctl"
|
JOURNALCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-journalctl"
|
||||||
OVERLAY_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-overlay"
|
OVERLAY_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-overlay"
|
||||||
SCRIPT_SANDBOX_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-script-sandbox"
|
SCRIPT_SANDBOX_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-script-sandbox"
|
||||||
|
APPLY_CAKE_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-apply-cake"
|
||||||
SANDBOX_RESOLV_CONF = DEPLOY / "files/etc/left4me/sandbox-resolv.conf"
|
SANDBOX_RESOLV_CONF = DEPLOY / "files/etc/left4me/sandbox-resolv.conf"
|
||||||
CAKE_ENV = DEPLOY / "files/etc/left4me/cake.env"
|
CAKE_ENV = DEPLOY / "files/etc/left4me/cake.env"
|
||||||
SUDOERS = DEPLOY / "files/etc/sudoers.d/left4me"
|
SUDOERS = DEPLOY / "files/etc/sudoers.d/left4me"
|
||||||
|
|
@ -770,3 +771,35 @@ def test_cake_env_template_documents_required_knobs():
|
||||||
# Empty defaults: shaper unit no-ops with a journal warning when unset.
|
# Empty defaults: shaper unit no-ops with a journal warning when unset.
|
||||||
assert "LEFT4ME_UPLINK_MBIT=" in text
|
assert "LEFT4ME_UPLINK_MBIT=" in text
|
||||||
assert "LEFT4ME_UPLINK_IFACE=" in text
|
assert "LEFT4ME_UPLINK_IFACE=" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_cake_helper_supports_apply_and_clear_modes():
|
||||||
|
assert APPLY_CAKE_HELPER.is_file()
|
||||||
|
text = APPLY_CAKE_HELPER.read_text()
|
||||||
|
|
||||||
|
assert text.startswith("#!/bin/sh")
|
||||||
|
# Both knobs are read from the env file.
|
||||||
|
assert "LEFT4ME_UPLINK_MBIT" in text
|
||||||
|
assert "LEFT4ME_UPLINK_IFACE" in text
|
||||||
|
assert ". /etc/left4me/cake.env" in text
|
||||||
|
# Iface fallback to default route.
|
||||||
|
assert "ip -4 route show default" in text
|
||||||
|
# Two modes; default to apply.
|
||||||
|
assert "mode=${1:-apply}" in text
|
||||||
|
assert 'apply)' in text and 'clear)' in text
|
||||||
|
# Apply: idempotent `tc qdisc replace` with the documented flags.
|
||||||
|
assert "tc qdisc replace" in text
|
||||||
|
assert "cake" in text
|
||||||
|
assert "bandwidth" in text
|
||||||
|
assert "internet" in text
|
||||||
|
assert "diffserv4" in text
|
||||||
|
assert "dual-dsthost" in text
|
||||||
|
# Clear: tolerates a missing qdisc.
|
||||||
|
assert "tc qdisc del" in text
|
||||||
|
assert "|| true" in text
|
||||||
|
# Fail-soft on missing config.
|
||||||
|
assert "LEFT4ME_UPLINK_MBIT unset" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_cake_helper_passes_shell_syntax_check():
|
||||||
|
subprocess.run(["sh", "-n", str(APPLY_CAKE_HELPER)], check=True)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue