Last edited 3 weeks ago
by Peter A. Smode

KitsNet Operations:Services:Mail:Containerized Mail:Junk to Bayes Design, Build and Deploy Guide

1 KitsNet PMG Junk-to-Bayes Training Design, Build, Deployment, and Operations Guide[edit | edit source]

Purpose: Document the final production design, build, deployment, security model, operation, and reporting for the KitsNet reviewed Bayesian spam-learning subsystem.

Production status: Deployed and operational as of 2026-08-31.

Applies to: KitsNet production mail environment; Docker Swarm mail service mail_mail-core; manager mgr1; workers wrk1 and wrk2; Proxmox Mail Gateway host dusse; Dovecot Maildir storage on shared nas.dkr storage.

Source repository: /srv/git/KNdkr/src/junk-bayes-review/

Production commits:

  • 1ea9e144befa3ac774752ed0a8a7d5a615f608fe — mail: add reviewed Bayesian spam learning pipeline
  • be3fc3c46c97a900340414d07a966f9368544350 — mail: add Bayesian learning activity reports

Important: This MediaWiki build/operations guide is maintained in MediaWiki, not in the Git repository. Git contains the operational source, deployment metadata, public keys, and systemd unit files.

1.1 Production workflow[edit | edit source]

The production workflow deliberately separates an end user's Junk decision from authorization to alter the system-wide PMG Bayesian classifier.

User moves unwanted mail to Junk
        |
        v
user .Junk/cur or .Junk/new
        |
        | KNBayesReview collection
        | 30-day age gate
        | 120-second settle period
        v
reroot@kitsnet.us/Bayesian
        |
        | administrator reviews
        |
        +---- delete ----------> not trained
        |
        | move approved spam
        v
reroot@kitsnet.us/Bayesian/Process
        |
        | KNBayesReview approval processing
        | restricted SSH
        v
dusse
        |
        | KNBayesSSHCommand
        | KNBayesLearnSpam
        v
sa-learn as pmg-smtp-filter
        |
        v
PMG Bayes database
        |
        | success recorded in SQLite
        v
review copy removed from Bayesian/Process

The authorization rule is:

User Junk action = nomination for administrator review
Administrator move to Bayesian/Process = authorization for global spam training

There is no Completed folder and no Rejected folder.

1.2 Final production architecture[edit | edit source]

1.2.1 mgr1[edit | edit source]

mgr1 is the orchestration host.

Production components:

  • /usr/local/sbin/KNBayesReview — main Python collector, approval processor, state recorder, and report-data generator.
  • /usr/local/sbin/KNBayesLocateMailCore — locates the worker currently running mail_mail-core.
  • /usr/local/sbin/KNBayesDailyReport — generates and sends the daily HTML activity report.
  • /var/lib/knbayes/KNBayesReview.sqlite3 — persistent processing/audit database.
  • /var/lib/knbayes/.ssh/id_ed25519_mailcore — private key for restricted worker access.
  • /var/lib/knbayes/.ssh/id_ed25519_pmg — private key for restricted PMG access.
  • /var/lib/knbayes/.ssh/known_hosts — pinned SSH host keys.
  • knbayes-review.service / knbayes-review.timer — five-minute production processor.
  • knbayes-daily-report.service / knbayes-daily-report.timer — once-daily HTML report.

The main service runs as the dedicated unprivileged account knbayes.

The only root privilege granted on mgr1 is:

knbayes ALL=(root) NOPASSWD: /usr/local/sbin/KNBayesLocateMailCore

1.2.2 wrk1 and wrk2[edit | edit source]

Both workers carry an identical restricted helper:

/usr/local/sbin/KNBayesMailCoreExec

The helper is invoked through a forced SSH command. The worker account cannot use the key to obtain an interactive shell or execute arbitrary commands.

Worker sudo policy:

Defaults:knbayes env_keep += "SSH_ORIGINAL_COMMAND"
knbayes ALL=(root) NOPASSWD: /usr/local/sbin/KNBayesMailCoreExec

Authorized-key policy:

restrict,command="sudo /usr/local/sbin/KNBayesMailCoreExec" ssh-ed25519 ... KNBayes mail-core worker access

The helper supports only the explicitly implemented operations:

scan
stage
review-count
process-scan
process-fetch
process-complete

Any other operation is rejected.

1.2.3 dusse[edit | edit source]

PMG learning is performed locally on dusse.

Production components:

  • /usr/local/sbin/KNBayesSSHCommand — forced-command SSH dispatcher.
  • /usr/local/sbin/KNBayesLearnSpam — validated local SpamAssassin learning helper.
  • /var/lib/knbayes/incoming/ — transient validated incoming-message area.
  • Dedicated account knbayes.
  • PMG Bayes database owned and used by pmg-smtp-filter.

Sudo policy:

knbayes ALL=(root) NOPASSWD: /usr/local/sbin/KNBayesLearnSpam

Authorized-key policy:

restrict,command="/usr/local/sbin/KNBayesSSHCommand" ssh-ed25519 ... KNBayes PMG learner access

The PMG forced command accepts only:

status
receive SHA256
learn SHA256

Arbitrary commands are rejected with exit status 64.

1.3 Mailbox layout[edit | edit source]

The host-side production mailbox root used by the worker helper is:

/srv/docker-nfs-mail/mailboxes

The production domain is:

kitsnet.us

Candidate source folders are restricted to:

/srv/docker-nfs-mail/mailboxes/kitsnet.us/<user>/.Junk/cur/
/srv/docker-nfs-mail/mailboxes/kitsnet.us/<user>/.Junk/new/
/srv/docker-nfs-mail/mailboxes/kitsnet.us/<user>/.Junk Email/cur/
/srv/docker-nfs-mail/mailboxes/kitsnet.us/<user>/.Junk Email/new/

The administrator review identity is:

reroot@kitsnet.us

Review folders:

Bayesian
Bayesian/Process

The corresponding host-side review roots include:

/srv/docker-nfs-mail/mailboxes/kitsnet.us/reroot/.Bayesian
/srv/docker-nfs-mail/mailboxes/kitsnet.us/reroot/.Bayesian.Process

1.4 Candidate collection[edit | edit source]

KNBayesReview run first performs candidate collection.

The worker-side scan operation:

  1. Searches only approved Junk Maildir paths.
  2. Ignores messages older than 30 days.
  3. Requires a 120-second settle period.
  4. Computes SHA-256 over the complete stored RFC822 message.
  5. Extracts the Message-ID when present.
  6. Returns candidate metadata to mgr1.

The default production values are:

max_message_age_days=30
settle_seconds=120

For an eligible candidate, mgr1 invokes the worker stage operation with the expected SHA-256 and a base64-encoded source path.

The worker then:

  1. Revalidates that the path is inside an approved Junk Maildir.
  2. Rejects symbolic links.
  3. Verifies that the file still exists.
  4. Recomputes SHA-256 and requires an exact match.
  5. Determines the source user and Junk mailbox.
  6. Requires a Message-ID.
  7. Locates exactly one corresponding Dovecot message.
  8. Uses doveadm save to place the exact RFC822 source in reroot@kitsnet.us/Bayesian.
  9. Finds and verifies a byte-identical review copy by SHA-256.
  10. Only after the review copy is verified, expunges the exact source UID from the user's Junk mailbox.
  11. Confirms the original Message-ID is no longer present in that source Junk mailbox.
  12. Returns result=staged_and_source_removed.

Important implementation note: The final deployed implementation therefore differs from the earlier design draft that said the user's Junk copy would remain untouched. In production, successful staging creates and verifies the administrator review copy and then removes the exact source Junk message. The verified review copy becomes the retained copy for the review workflow.

1.5 Administrator review[edit | edit source]

Candidate collection never trains PMG.

The administrator reviews:

reroot@kitsnet.us/Bayesian

Actions:

  • Approve spam: move the message to Bayesian/Process.
  • Decline training: delete the message from Bayesian.
  • Leave undecided: leave it in Bayesian.

Only presence in Bayesian/Process is treated as training authorization.

1.6 Approved-message processing[edit | edit source]

The processor scans Bayesian/Process through the restricted worker process-scan operation.

For every approved message:

  1. The worker calculates SHA-256 and returns its path, modification time, and Message-ID.
  2. mgr1 records the approval in SQLite.
  3. mgr1 requests the exact message through process-fetch.
  4. The worker revalidates the path and SHA-256 before returning the bytes.
  5. mgr1 opens restricted SSH to knbayes@dusse and executes receive SHA256.
  6. KNBayesSSHCommand writes the incoming bytes to a private temporary file.
  7. The received SHA-256 must match the requested digest.
  8. The verified file is atomically placed at /var/lib/knbayes/incoming/SHA256.eml.
  9. mgr1 invokes the restricted learn SHA256 operation.
  10. KNBayesLearnSpam learns the message as spam under the PMG filtering identity.
  11. On successful learning, the incoming file is removed.
  12. mgr1 records learned_at in SQLite.
  13. mgr1 invokes worker process-complete.
  14. The worker removes the exact approved message from Bayesian/Process.
  15. mgr1 records completed_at.

The successful production log sequence is:

APPROVED sha256=<digest>
         path=<Bayesian.Process path>
         message-id=<message-id>
         PMG=LEARNED
         RESULT=COMPLETED_AND_REMOVED

1.7 PMG learning behavior[edit | edit source]

The learning operation uses the PMG SpamAssassin Bayes database associated with pmg-smtp-filter.

The production Bayes status can be queried through the restricted status operation or locally on dusse.

At final end-to-end validation on 2026-08-31:

nspam = 16
nham  = 407

The successful production test increased nspam from 15 to 16.

The Bayes database remains PMG-owned; mgr1 and the workers never receive direct filesystem access to it.

1.8 SQLite state and audit history[edit | edit source]

The database is:

/var/lib/knbayes/KNBayesReview.sqlite3

The production schema contains two principal tables.

1.8.1 candidates[edit | edit source]

CREATE TABLE candidates (
    sha256 TEXT PRIMARY KEY,
    source_user TEXT NOT NULL,
    source_mailbox TEXT NOT NULL,
    source_path TEXT NOT NULL,
    first_seen INTEGER NOT NULL,
    staged_at INTEGER,
    review_path TEXT,
    source_removed_at INTEGER,
    last_error TEXT
);

This records candidate discovery, successful staging, the verified review path, source removal, and errors.

1.8.2 approvals[edit | edit source]

CREATE TABLE approvals (
    sha256 TEXT PRIMARY KEY,
    process_path TEXT NOT NULL,
    message_id TEXT,
    first_seen INTEGER NOT NULL,
    received_at INTEGER,
    learned_at INTEGER,
    completed_at INTEGER,
    last_error TEXT
);

This records administrator-approved messages, receipt by PMG, successful Bayes learning, completion, and errors.

No message bodies are stored in SQLite.

1.9 Production scheduling[edit | edit source]

1.9.1 Five-minute processor[edit | edit source]

Service:

/etc/systemd/system/knbayes-review.service

Source-controlled copy:

src/junk-bayes-review/deploy/mgr1/knbayes-review.service

Unit:

[Unit]
Description=KitsNet Bayesian spam review processor
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=knbayes
Group=knbayes
ExecStart=/usr/local/sbin/KNBayesReview run

Timer:

/etc/systemd/system/knbayes-review.timer

Source-controlled copy:

src/junk-bayes-review/deploy/mgr1/knbayes-review.timer

Production timer:

[Unit]
Description=Run KitsNet Bayesian spam review processor

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target

This timer performs collection and approved-message processing. It does not send the daily report.

1.9.2 Daily report[edit | edit source]

The daily report is a separate systemd job.

Service:

/etc/systemd/system/knbayes-daily-report.service

Timer:

/etc/systemd/system/knbayes-daily-report.timer

The timer runs once per day at:

23:55 local time

The production recipient is:

postmaster@lan.kitsnet.us

Using the lan.kitsnet.us address intentionally routes the report directly through the internal mail system rather than sending it toward the public MX path.

1.10 Daily HTML reporting[edit | edit source]

KNBayesDailyReport generates a multipart HTML/text email using the persistent SQLite history and live subsystem state.

The report includes:

  • today's Junk messages collected;
  • today's successfully learned spam;
  • today's completed/removed approvals;
  • currently recorded errors;
  • last 7 calendar days totals;
  • last 30 calendar days totals;
  • number awaiting administrator review;
  • number awaiting processing;
  • outstanding recorded errors;
  • current PMG nspam;
  • current PMG nham;
  • recent successful learning events; and
  • overall status.

A representative report after initial production validation showed:

TODAY
  Junk messages collected:       3
  Successfully learned as spam:  1
  Completed and removed:         1
  Errors currently recorded:     0

LAST 7 CALENDAR DAYS
  Collected:                     3
  Learned:                       1
  Completed:                     1
  Errors currently recorded:     0

LAST 30 CALENDAR DAYS
  Collected:                     3
  Learned:                       1
  Completed:                     1
  Errors currently recorded:     0

CURRENT STATE
  Awaiting administrator review: 2
  Awaiting processing:           0
  Outstanding recorded errors:   0

PMG BAYES DATABASE
  Spam learned total:            16
  Ham learned total:             407

STATUS: OK

The HTML presentation was validated in the production mail client before the reporting code was committed.

1.11 Internal report delivery[edit | edit source]

The initial test to postmaster@kitsnet.us followed public DNS/MX routing and was deferred. Production reporting therefore uses:

postmaster@lan.kitsnet.us

Verified delivery path:

mgr1 Postfix
    |
    v
mailroom.lan.kitsnet.us [192.168.14.51]:25
    |
    v
postmaster@lan.kitsnet.us

A production test returned:

dsn=2.0.0
status=sent

The obsolete public-address test message was removed from the mgr1 queue after the internal route was validated.

1.12 Source control[edit | edit source]

All operational source is protected in the KitsNet Docker Git repository.

Production tree:

src/junk-bayes-review/
    .gitignore
    KNBayesDailyReport
    KNBayesLearnSpam
    KNBayesLocateMailCore
    KNBayesMailCoreExec
    KNBayesReview.py
    KNBayesSSHCommand
    deploy/
        dusse/
            authorized_keys.entry
            id_ed25519_pmg.pub
            knbayes-learn-spam
        mgr1/
            knbayes-locate-mailcore
            knbayes-review.service
            knbayes-review.timer
            knbayes-daily-report.service
            knbayes-daily-report.timer
        worker/
            authorized_keys.entry
            id_ed25519_mailcore.pub
            knbayes-mailcore

Private keys are deliberately not stored in Git.

The source tree contains the two public keys and the exact forced-command authorization entries required to reconstruct the restricted trust relationships.

The final reporting commit is:

be3fc3c46c97a900340414d07a966f9368544350
mail: add Bayesian learning activity reports

After the final push:

HEAD          = be3fc3c46c97a900340414d07a966f9368544350
origin/master = be3fc3c46c97a900340414d07a966f9368544350
ahead/behind  = 0 / 0

The unrelated local modification to stacks/maildb-support/stack.yml was intentionally excluded from both Bayesian commits.

1.13 Security model[edit | edit source]

1.13.1 Separate keys for separate trust paths[edit | edit source]

There are two dedicated Ed25519 key pairs:

  • mgr1 → workers: id_ed25519_mailcore
  • mgr1 → dusse: id_ed25519_pmg

They are not interchangeable.

The private keys remain only in protected runtime storage beneath /var/lib/knbayes/.ssh/ on mgr1.

Only the public keys are stored in Git.

1.13.2 Forced commands[edit | edit source]

Worker access is restricted to:

sudo /usr/local/sbin/KNBayesMailCoreExec

PMG access is restricted to:

/usr/local/sbin/KNBayesSSHCommand

OpenSSH restrict prevents normal interactive/forwarding use of these authorized keys.

1.13.3 Command validation[edit | edit source]

The worker helper accepts only a tiny command protocol and validates:

  • operation name;
  • argument count;
  • SHA-256 format;
  • base64-decoded paths;
  • permitted Maildir roots;
  • regular-file status;
  • symbolic-link rejection;
  • source content hash;
  • Dovecot search result count.

The PMG helper validates:

  • operation name;
  • argument count;
  • SHA-256 format;
  • incoming content SHA-256;
  • duplicate incoming filenames;
  • exact incoming path.

This provides defense in depth even if one orchestration component is compromised.

1.13.4 No general root SSH[edit | edit source]

The subsystem does not require root SSH between mgr1, the workers, and dusse.

The knbayes account receives only narrowly scoped sudo permissions for the exact helper required on each host.

1.14 Failure and retry behavior[edit | edit source]

1.14.1 Candidate collection failure[edit | edit source]

If scanning or staging fails:

  • the run returns nonzero;
  • the error is recorded where possible;
  • no PMG learning occurs.

1.14.2 PMG receive failure[edit | edit source]

If message transfer or SHA-256 verification fails:

  • the message remains in Bayesian/Process;
  • learned_at is not recorded;
  • a later five-minute run can retry.

1.14.3 sa-learn failure[edit | edit source]

If KNBayesLearnSpam or sa-learn fails:

  • the PMG incoming file is retained when appropriate for safe diagnosis/retry;
  • mgr1 does not record successful learning;
  • the approved review copy is not completed/removed.

1.14.4 Completion failure[edit | edit source]

If PMG learning succeeds but worker completion fails, SQLite's learned_at prevents unnecessary relearning on the next attempt. The processor can retry the completion step.

1.14.5 No-op run[edit | edit source]

A successful run with no candidates and no approvals exits successfully. Systemd records a normal successful oneshot execution.

1.15 Operational commands[edit | edit source]

1.15.1 Check both timers[edit | edit source]

Run on mgr1:

for i in {1..10}; do echo; done

echo '===== TIMER ENABLEMENT ====='
systemctl is-enabled knbayes-review.timer
systemctl is-enabled knbayes-daily-report.timer

echo
echo '===== TIMER STATE ====='
systemctl is-active knbayes-review.timer
systemctl is-active knbayes-daily-report.timer

echo
echo '===== NEXT RUNS ====='
systemctl list-timers \
  knbayes-review.timer \
  knbayes-daily-report.timer \
  --no-pager

1.15.2 View recent processor activity[edit | edit source]

for i in {1..10}; do echo; done

journalctl \
  -u knbayes-review.service \
  --since '24 hours ago' \
  --no-pager

1.15.3 View recent daily-report activity[edit | edit source]

for i in {1..10}; do echo; done

journalctl \
  -u knbayes-daily-report.service \
  --since '7 days ago' \
  --no-pager

1.15.4 Generate a report without sending email[edit | edit source]

for i in {1..10}; do echo; done

sudo -u knbayes \
  /usr/local/sbin/KNBayesReview report

1.15.5 Send a report manually[edit | edit source]

for i in {1..10}; do echo; done

sudo -u knbayes \
  /usr/local/sbin/KNBayesDailyReport

This sends a real report to postmaster@lan.kitsnet.us.

1.15.6 View current PMG Bayes counts from mgr1[edit | edit source]

for i in {1..10}; do echo; done

sudo -u knbayes \
  ssh \
    -i /var/lib/knbayes/.ssh/id_ed25519_pmg \
    -o BatchMode=yes \
    -o StrictHostKeyChecking=yes \
    -o UserKnownHostsFile=/var/lib/knbayes/.ssh/known_hosts \
    knbayes@dusse \
    status

Useful values include nspam, nham, ntokens, and Bayes timestamps.

1.15.7 View current review queue count[edit | edit source]

for i in {1..10}; do echo; done

NODE="$(
  sudo -u knbayes \
    sudo /usr/local/sbin/KNBayesLocateMailCore
)"

echo "mail_core_node=$NODE"

sudo -u knbayes \
  ssh \
    -i /var/lib/knbayes/.ssh/id_ed25519_mailcore \
    -o BatchMode=yes \
    -o StrictHostKeyChecking=yes \
    -o UserKnownHostsFile=/var/lib/knbayes/.ssh/known_hosts \
    "knbayes@$NODE" \
    review-count

1.15.8 Inspect processing history[edit | edit source]

for i in {1..10}; do echo; done

sqlite3 -header -column \
  /var/lib/knbayes/KNBayesReview.sqlite3 \
  "SELECT
       datetime(learned_at,'unixepoch','localtime') AS learned,
       message_id,
       substr(sha256,1,16) AS sha256
   FROM approvals
   WHERE learned_at IS NOT NULL
   ORDER BY learned_at DESC
   LIMIT 25;"

1.15.9 Inspect outstanding errors[edit | edit source]

for i in {1..10}; do echo; done

sqlite3 -header -column \
  /var/lib/knbayes/KNBayesReview.sqlite3 \
  "SELECT 'candidate' AS type,
          substr(sha256,1,16) AS sha256,
          last_error
     FROM candidates
    WHERE last_error IS NOT NULL
   UNION ALL
   SELECT 'approval',
          substr(sha256,1,16),
          last_error
     FROM approvals
    WHERE last_error IS NOT NULL;"

1.16 Deployment and rebuild procedure[edit | edit source]

1.16.1 Repository source[edit | edit source]

Start on mgr1:

for i in {1..10}; do echo; done

cd /srv/git/KNdkr

git status --short
git log -2 --oneline

Expected production commits include:

be3fc3c mail: add Bayesian learning activity reports
1ea9e14 mail: add reviewed Bayesian spam learning pipeline

1.16.2 Install mgr1 programs[edit | edit source]

for i in {1..10}; do echo; done

cd /srv/git/KNdkr

sudo install -o root -g root -m 0755 \
  src/junk-bayes-review/KNBayesReview.py \
  /usr/local/sbin/KNBayesReview

sudo install -o root -g root -m 0755 \
  src/junk-bayes-review/KNBayesLocateMailCore \
  /usr/local/sbin/KNBayesLocateMailCore

sudo install -o root -g root -m 0755 \
  src/junk-bayes-review/KNBayesDailyReport \
  /usr/local/sbin/KNBayesDailyReport

1.16.3 Install worker helper[edit | edit source]

Install the same source-controlled helper on both wrk1 and wrk2:

for i in {1..10}; do echo; done

cd /srv/git/KNdkr

scp src/junk-bayes-review/KNBayesMailCoreExec \
  wrk1:/tmp/KNBayesMailCoreExec

scp src/junk-bayes-review/KNBayesMailCoreExec \
  wrk2:/tmp/KNBayesMailCoreExec

ssh wrk1 \
  'sudo install -o root -g root -m 0755 /tmp/KNBayesMailCoreExec /usr/local/sbin/KNBayesMailCoreExec'

ssh wrk2 \
  'sudo install -o root -g root -m 0755 /tmp/KNBayesMailCoreExec /usr/local/sbin/KNBayesMailCoreExec'

After installation, verify the source and both installed copies have identical SHA-256 checksums.

1.16.4 Install dusse programs[edit | edit source]

The source-controlled PMG programs are:

src/junk-bayes-review/KNBayesLearnSpam
src/junk-bayes-review/KNBayesSSHCommand

Install them on dusse as root-owned mode 0755 files in /usr/local/sbin/ and verify checksums against Git.

1.16.5 Install systemd units[edit | edit source]

On mgr1:

for i in {1..10}; do echo; done

cd /srv/git/KNdkr

sudo install -o root -g root -m 0644 \
  src/junk-bayes-review/deploy/mgr1/knbayes-review.service \
  /etc/systemd/system/knbayes-review.service

sudo install -o root -g root -m 0644 \
  src/junk-bayes-review/deploy/mgr1/knbayes-review.timer \
  /etc/systemd/system/knbayes-review.timer

sudo install -o root -g root -m 0644 \
  src/junk-bayes-review/deploy/mgr1/knbayes-daily-report.service \
  /etc/systemd/system/knbayes-daily-report.service

sudo install -o root -g root -m 0644 \
  src/junk-bayes-review/deploy/mgr1/knbayes-daily-report.timer \
  /etc/systemd/system/knbayes-daily-report.timer

sudo systemctl daemon-reload

1.16.6 Validate before enabling[edit | edit source]

for i in {1..10}; do echo; done

echo '===== SOURCE SYNTAX ====='
python3 -m py_compile \
  /usr/local/sbin/KNBayesReview \
  /usr/local/sbin/KNBayesDailyReport

echo
echo '===== PIPELINE DRY RUN ====='
sudo -u knbayes \
  /usr/local/sbin/KNBayesReview run --dry-run

echo
echo '===== LIVE REPORT ====='
sudo -u knbayes \
  /usr/local/sbin/KNBayesReview report

1.16.7 Enable timers[edit | edit source]

for i in {1..10}; do echo; done

sudo systemctl enable --now knbayes-review.timer
sudo systemctl enable --now knbayes-daily-report.timer

echo
systemctl list-timers \
  knbayes-review.timer \
  knbayes-daily-report.timer \
  --no-pager

1.17 Acceptance tests completed[edit | edit source]

The production deployment was validated with the following tests:

Test Result
Worker forced-command status/access Passed
Arbitrary worker command rejection Passed
PMG forced-command status Passed
Arbitrary PMG command rejection Passed with exit status 64
Candidate collection from real Junk folder Passed
Verified staging into Bayesian Passed
Administrator move into Bayesian/Process Passed
Restricted process-scan Passed
Exact message fetch with SHA-256 verification Passed
PMG receive with SHA-256 verification Passed
sa-learn under PMG identity Passed
PMG nspam increment Passed, 15 → 16
Automatic completion/removal from Process Passed
Five-minute unattended timer processing Passed
Live report generation Passed
HTML report rendering Passed
Internal email delivery Passed
Daily report timer Enabled and active
Git private-key scan No private-key material committed
Git local/remote synchronization Passed, ahead/behind 0/0

1.18 Production verification example[edit | edit source]

The first fully unattended administrator-approved production message completed as follows:

APPROVED sha256=8a68f0a10dcce2c69c46fbed3419acc4ae27c0af51c71a38a31dc6e1988e4a3b
message-id=<074446_250524065538ziwkcqtyezmnamapykzm@wwwcologuard.com>
PMG=LEARNED
RESULT=COMPLETED_AND_REMOVED

approved_messages=1
learned=1
completed=1
errors=0

Afterward:

Bayesian/Process messages=0
PMG nspam=16
PMG nham=407

This demonstrated the complete production path without manual execution of the learning operation.

1.19 Normal administrator procedure[edit | edit source]

Day-to-day use is intentionally minimal:

  1. Users move unwanted messages to their normal Junk folder.
  2. Within approximately five minutes, eligible candidates are moved into reroot@kitsnet.us/Bayesian.
  3. Review that mailbox normally.
  4. Delete anything that should not train the global classifier.
  5. Move confirmed spam into Bayesian/Process.
  6. Within approximately five minutes, approved messages are learned on PMG and removed from Process.
  7. Review the daily HTML report delivered to postmaster@lan.kitsnet.us at 23:55.

No SSH session or manual sa-learn invocation is required for normal operation.

1.20 Rollback and emergency stop[edit | edit source]

To stop automated collection and processing immediately without altering PMG's existing Bayes database:

for i in {1..10}; do echo; done

sudo systemctl disable --now knbayes-review.timer

To stop only daily email reporting:

for i in {1..10}; do echo; done

sudo systemctl disable --now knbayes-daily-report.timer

Disabling the processor does not delete:

  • SQLite history;
  • review mailbox contents;
  • PMG Bayes data; or
  • Git source.

To revoke mgr1's PMG learning authority, remove the corresponding forced-command public-key entry from /var/lib/knbayes/.ssh/authorized_keys on dusse.

To revoke mgr1's worker authority, remove the worker forced-command public-key entry on both wrk1 and wrk2.

Do not delete the PMG Bayes database merely to disable this workflow.

1.21 Monitoring and future enhancements[edit | edit source]

The daily report now provides the primary human-readable trend view. SQLite retains the detailed event history from which daily, 7-day, and 30-day totals are calculated.

Potential future Zabbix integration can expose:

  • processor timer/service health;
  • last successful processor run;
  • review queue count;
  • Process queue count;
  • outstanding SQLite errors;
  • daily learned count;
  • PMG nspam;
  • PMG nham;
  • unexpected decreases in Bayes totals.

A quiet day with zero learned spam is not itself an error.

A future ham-correction workflow should use an explicit administrator action or dedicated folder. Merely moving a message out of Junk is too ambiguous to be interpreted automatically as ham.

1.22 Design decisions[edit | edit source]

Decision Production selection Reason
User spam action Move to normal Junk folder Native client behavior; no forwarding workflow
Meaning of Junk action Nomination for review Prevents a user action from directly altering global Bayes
Review queue reroot@kitsnet.us/Bayesian Normal IMAP administrator review
Approval action Move to Bayesian/Process Explicit administrator authorization
Decline action Delete from Bayesian No Rejected-folder maintenance
Completion SQLite state plus removal from Process No Completed-folder maintenance
Candidate maximum age 30 days Avoids bulk ingestion of uncertain historical Junk
Settle period 120 seconds Avoids racing recent mailbox changes
Content identity SHA-256 Exact end-to-end verification and deduplication
State store SQLite Durable, inspectable event history
Worker access Restricted SSH forced command No general remote shell/root trust
PMG access Separate restricted SSH forced command Separates mailbox and learner privileges
PMG learner identity pmg-smtp-filter Uses PMG's live Bayes ownership/context
Processor schedule systemd timer every 5 minutes Responsive unattended processing
Reporting schedule Daily at 23:55 One concise daily operational summary
Report destination postmaster@lan.kitsnet.us Forces internal mail routing
Source protection Git under src/junk-bayes-review Reproducible and auditable deployment
Private keys in Git Never Secrets remain runtime-only

1.23 Authoritative implementation notes[edit | edit source]

The following points supersede earlier drafts of this guide:

  1. The subsystem is implemented on mgr1 with restricted remote helpers on the active mail-core worker and dusse; it is not a separate Swarm harvester container.
  2. Scheduling is performed by knbayes-review.timer on mgr1 every five minutes.
  3. Candidate collection stages a verified copy into Bayesian and then removes the exact source Junk message.
  4. Only an administrator move into Bayesian/Process authorizes PMG learning.
  5. PMG transport is a verified message transfer using receive SHA256, followed by a separate learn SHA256 operation.
  6. PMG learning is performed by KNBayesLearnSpam under the PMG filtering identity.
  7. SQLite tables candidates and approvals are the authoritative processing history.
  8. Reporting is built into the production subsystem.
  9. A separate daily systemd timer sends an HTML report once per day at 23:55 to postmaster@lan.kitsnet.us.
  10. Operational source and deployment metadata are Git-protected; private keys are not.
  11. There are no Completed or Rejected folders.

Any older documentation describing a direct Junk-to-PMG learner, a Swarm mail_junk-bayes-review service, a 15-minute schedule, KNBayesReceive, tar-stream submission, or preservation of the original Junk copy after successful staging is superseded by this production guide.