This tutorial provides a complete, production-ready guide to integrating the Mark as Junk plugin in Roundcube Webmail with SpamAssassin (sa-learn) on a DirectAdmin server. Using sudoers permissions for the webapps user ensures that Bayes training updates the correct per-user SpamAssassin database securely and without permission errors.
Step 1: Create the Main Learning Script
Create the master script that receives the email payload from Roundcube and passes it to sa-learn under the specific DirectAdmin user context:
cat > /usr/local/bin/rc-sa-learn.sh << 'SCRIPT'
#!/bin/bash
TYPE="$1"
DOMAIN="$2"
MSGFILE="$3"
DAUSER=$(grep -i "^${DOMAIN}:" /etc/virtual/domainowners 2>/dev/null | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [ -z "$DAUSER" ]; then
echo "$(date): ERROR - could not resolve DA user for domain $DOMAIN" >> /var/log/rc-sa-learn.log
exit 1
fi
BAYESPATH="/home/${DAUSER}/.spamassassin/bayes"
if [ "$TYPE" = "spam" ]; then
sudo -u "$DAUSER" /usr/bin/sa-learn --spam --dbpath="$BAYESPATH" "$MSGFILE" >> /var/log/rc-sa-learn.log 2>&1
elif [ "$TYPE" = "ham" ]; then
sudo -u "$DAUSER" /usr/bin/sa-learn --ham --dbpath="$BAYESPATH" "$MSGFILE" >> /var/log/rc-sa-learn.log 2>&1
fi
if [ $? -ne 0 ]; then
echo "$(date): WARNING - sa-learn failed for user $DAUSER (missing sudoers entry? run /root/bootstrap-sa-learn-sudoers.sh)" >> /var/log/rc-sa-learn.log
fi
SCRIPT
chmod +x /usr/local/bin/rc-sa-learn.sh
touch /var/log/rc-sa-learn.log
chmod 666 /var/log/rc-sa-learn.log
Step 2: Automate Sudoers Rules for New Users (DirectAdmin Hook)
Set up a DirectAdmin post-user-creation script to automatically grant the webapps user passwordless sudo permissions for running sa-learn under newly created accounts:
mkdir -p /usr/local/directadmin/scripts/custom
cat > /usr/local/directadmin/scripts/custom/user_create_post.sh << 'SCRIPT'
#!/bin/sh
DAUSER="${username}"
if [ -z "$DAUSER" ]; then
exit 0
fi
SUDOFILE="/etc/sudoers.d/sa-learn-${DAUSER}"
if [ ! -f "$SUDOFILE" ]; then
cat > "$SUDOFILE" << EOF
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --spam --dbpath=/home/${DAUSER}/.spamassassin/bayes *
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --ham --dbpath=/home/${DAUSER}/.spamassassin/bayes *
EOF
chmod 440 "$SUDOFILE"
visudo -cf "$SUDOFILE" >/dev/null 2>&1 || rm -f "$SUDOFILE"
fi
exit 0;
SCRIPT
chmod 700 /usr/local/directadmin/scripts/custom/user_create_post.sh
chown diradmin:diradmin /usr/local/directadmin/scripts/custom/user_create_post.sh
Step 3: Provision Sudoers Rules for Existing Users
Create and execute a bootstrap script to generate the appropriate sudoers entries for all existing DirectAdmin users:
cat > /root/bootstrap-sa-learn-sudoers.sh << 'SCRIPT'
#!/bin/bash
while IFS=': ' read -r DOMAIN DAUSER; do
DAUSER=$(echo "$DAUSER" | tr -d '[:space:]')
[ -z "$DAUSER" ] && continue
SUDOFILE="/etc/sudoers.d/sa-learn-${DAUSER}"
if [ ! -f "$SUDOFILE" ]; then
cat > "$SUDOFILE" << EOF
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --spam --dbpath=/home/${DAUSER}/.spamassassin/bayes *
webapps ALL=(${DAUSER}) NOPASSWD: /usr/bin/sa-learn --ham --dbpath=/home/${DAUSER}/.spamassassin/bayes *
EOF
chmod 440 "$SUDOFILE"
visudo -cf "$SUDOFILE" >/dev/null 2>&1 && echo "Provisioned: $DAUSER" || { rm -f "$SUDOFILE"; echo "FAILED (invalid): $DAUSER"; }
fi
done < /etc/virtual/domainowners
SCRIPT
chmod +x /root/bootstrap-sa-learn-sudoers.sh
/root/bootstrap-sa-learn-sudoers.sh
Verify that the rules were created successfully:
ls /etc/sudoers.d/ | grep sa-learn
Step 4: Configure the Mark as Junk Plugin
Locate the active Roundcube webmail path and edit the plugin configuration:
RCPATH=$(find /var/www/webapps -maxdepth 1 -type d -iname "roundcubemail-*" | head -1)
echo "Live path: $RCPATH"
cp "$RCPATH/plugins/markasjunk/config.inc.php.dist" "$RCPATH/plugins/markasjunk/config.inc.php"
sed -i \
-e "s|\$config\['markasjunk_learning_driver'\] = null;|\$config['markasjunk_learning_driver'] = 'cmd_learn';|" \
-e "s|\$config\['markasjunk_spam_cmd'\] = null;|\$config['markasjunk_spam_cmd'] = '/usr/local/bin/rc-sa-learn.sh spam %d %f';|" \
-e "s|\$config\['markasjunk_ham_cmd'\] = null;|\$config['markasjunk_ham_cmd'] = '/usr/local/bin/rc-sa-learn.sh ham %d %f';|" \
-e "s|\$config\['markasjunk_debug'\] = false;|\$config['markasjunk_debug'] = true;|" \
"$RCPATH/plugins/markasjunk/config.inc.php"
Confirm the plugin configuration parameters:
grep -E "learning_driver|spam_cmd|ham_cmd|debug" "$RCPATH/plugins/markasjunk/config.inc.php"
Step 5: DirectAdmin CustomBuild Persistent Setup
Copy configuration and plugin files into the CustomBuild directory structure to ensure changes are retained across updates:
mkdir -p /usr/local/directadmin/custombuild/custom/roundcube
cp "$RCPATH/config/config.inc.php" /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php
chmod 640 /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php
RCPLUGINCONFIG="/usr/local/directadmin/custombuild/custom/roundcube/config.inc.php"
if ! grep -qE "['\"]markasjunk['\"]" "$RCPLUGINCONFIG"; then
sed -i -E "s|(\\\$config\['plugins'\]\s*=\s*\[[^]]*)\]|\1, 'markasjunk']|" "$RCPLUGINCONFIG"
fi
grep -E "plugins" "$RCPLUGINCONFIG"
mkdir -p /usr/local/directadmin/custombuild/custom/roundcube/plugins
cp -r "$RCPATH/plugins/markasjunk" /usr/local/directadmin/custombuild/custom/roundcube/plugins/
Step 6: Rebuild Roundcube and Restart Services
Recompile Roundcube via CustomBuild and restart Exim and SpamAssassin:
cd /usr/local/directadmin/custombuild
./build roundcube
systemctl restart spamd
da build exim_conf
Step 7: Verification and Logs
- Log into Roundcube Webmail interface.
- Select an email and click Mark as Junk (or As Not Junk).
- Check the log output to confirm successful processing:
tail -20 /var/log/rc-sa-learn.log
Maintenance: Future Upgrade Workflow
When upgrading Roundcube via CustomBuild in the future, automatically resolve the active installation directory and compare your custom configuration files against the newly installed distribution files to detect any new configuration flags:
RCPATH=$(find /var/www/webapps -maxdepth 1 -type d -iname "roundcubemail-*" | head -1)
# Check main configuration differences
diff /usr/local/directadmin/custombuild/custom/roundcube/config.inc.php "$RCPATH/config/config.inc.php"
# Check plugin configuration differences
diff /usr/local/directadmin/custombuild/custom/roundcube/plugins/markasjunk/config.inc.php "$RCPATH/plugins/markasjunk/config.inc.php.dist"
If any new $config[...] entries appear in the output, append or adapt them inside your custom configuration files under /usr/local/directadmin/custombuild/custom/roundcube/.
