Both came up while debugging something unrelated. Neither is a defect — both are places where NS8 behaves correctly but leaves you guessing.
Disclaimer: worked out together with an AI assistant (Claude); it did the analysis, I ran the commands and provided the output.
1. runagent is not on root’s PATH when you arrive via sudo
What you see:
[root@node03 ~]# runagent -l
-bash: runagent: command not found
[root@node03 ~]# ls -l /usr/local/bin/runagent
-rwxr-xr-x. 1 root root ... /usr/local/bin/runagent
The binary is there. The PATH is not:
/root/.local/bin:/root/bin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin
Cause is the stock RHEL/AlmaLinux line in /etc/sudoers:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
/usr/local/bin is not in it, and NS8 installs runagent exactly there. So anyone who reaches root via sudo — which is the normal path on a cloud-init installed node — cannot call runagent by name, while someone logging in as root directly can. That asymmetry is confusing, and the usual workaround found in older threads is to type the full path or switch to the admin user, neither of which fixes the cause.
/etc/profile.d/runagent.sh already exists and does set PATH, but only inside the agent environment (AGENT_STATE_DIR set) — it does not cover an interactive root shell.
Fix, as root:
echo 'Defaults secure_path = /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin' > /etc/sudoers.d/99-secure-path
chmod 0440 /etc/sudoers.d/99-secure-path
visudo -c
Keep the existing root shell open until you have verified sudo -i still works.
Suggestion: ship this drop-in with the node installation, or extend /etc/profile.d/runagent.sh to also cover interactive root shells. It would save a recurring stumble for anyone following documentation that says “run runagent -m <module>”.
2. The *.local template override deserves to be documented
If you need a configuration parameter that a module does not expose as a setting, the obvious move is to edit the generated file — for SOGo that is ~/.config/state/config/sogo.conf, bind-mounted into the container. That change survives exactly until the next module restart, because ExecStartPre=runagent expand-configuration regenerates the file every time. Easy to lose an hour to.
The supported mechanism is already in place. expand-configuration checks for a user-supplied Jinja2 template first:
if os.path.exists("templates/sogo.conf.local"):
template = jenv_custom.get_template('sogo.conf.local')
else:
template = jenv.get_template('sogo.conf')
So the durable way to change a parameter is:
runagent -m sogo1 bash -c 'cp $AGENT_INSTALL_DIR/templates/sogo.conf \
$AGENT_INSTALL_DIR/state/templates/sogo.conf.local'
# edit state/templates/sogo.conf.local, then:
runagent -m sogo1 systemctl --user restart sogo-app
The same pattern applies to SOGo.conf.local and cron.conf.local, and the state/templates directory is created by the unit itself (mkdir -p {config,backups,templates}), so it is clearly intended for this.
Caveat worth stating alongside it: the copy is a snapshot. If the shipped template changes in a module update, your .local version does not follow, and you may silently miss new parameters.
Suggestion: document this in the module/developer documentation, ideally with the caveat above. It is a genuinely good design — it is just not discoverable unless you read expand-configuration.
Best regards,
Thorsten