Quieting a jail’s mail, and a prompt that wasn’t mine

I was looking inside the index jail today and found this:

root@index:/ # ls -l /var/mail/
total 25
-rw--w----  1 root mail 51258 Oct 27 03:27 root

That jail doesn’t do much. Nobody reads that mail. I wanted it gone.

Along the way, I fixed something else which had been bothering me for a while: a broken prompt whenever I used jexec.

What was in the mailbox

Before turning anything off, I checked what was being sent:

grep '^Subject:' /var/mail/root | sort | uniq -c | sort -rn
  24 Subject: index daily security run output
  24 Subject: index daily run output
   3 Subject: index weekly run output

All of it is from periodic(8). Nothing from cron. That makes it simple.

Sending periodic output to log files

periodic can write its reports to a file instead of mailing them. These settings go in /etc/periodic.conf:

sysrc -f /etc/periodic.conf daily_output=/var/log/daily.log \
daily_status_security_output=/var/log/daily.log weekly_output=/var/log/weekly.log \
monthly_output=/var/log/monthly.log

FreeBSD’s default /etc/newsyslog.conf already rotates daily.log, weekly.log, and monthly.log, so there is nothing else to configure. The reports are still there if I want them. They just don’t pile up in a mailbox.

But… I think a better way is to disable cron and syslogd for that matter. Nothing runs in these jails.

# sysrc cron_enable="NO"
cron_enable: YES -> NO
# sysrc syslogd_enable="NO"
syslogd_enable: YES -> NO

Tonight’s run will confirm it. After that, I cleared the old mail:

: > /var/mail/root

If cron jobs had been sending mail too, sysrc cron_flags="-m ''" turns that off for every crontab. I didn’t need it here. I would not do that on a jail which does real work: a failing cron job would then fail silently.

The broken prompt

While doing this, I kept seeing this as my prompt:

[%F{cyan}%B%T%b%f %F{magenta}%m%b%f %F{green}%B%n%b%f %F{magenta}%B%~%b%f] %#%b%f

That is my zsh prompt, unexpanded. The shell was /bin/sh, and PS1 was in the environment:

% echo $0
/bin/sh
% env | grep -E '^(PS1|PROMPT)='
PS1=[%F{cyan}%B%T%b%f %F{magenta}%m%b%f ...

Something was exporting PS1. Every child shell inherited it. zsh understands those %F{…} escapes. sh does not, so it printed them as-is.

Neither zsh nor bash needs the prompt exported. It should be a plain shell variable.

Finding the export

The obvious culprit was in ~/.zshrc:

export PS1="[$cTIM%T$cSTD $cSRV%m$cSTD $cUSR%n$cSTD $cPWD%~$cSTD] $cPMT%#$cSTD "

I removed the export. A fresh login to dvl-ingress01 still had PS1 exported.

I checked allexport: not set. I checked whether ssh was sending it from pro05: no, only LANG and LC_*, and sshd on the other end accepts nothing extra.

Then I stopped guessing and asked zsh to trace its own startup. The env -u PS1 starts it with a clean slate. The -x trace prefixes each line with the file and line number it came from:

ssh -t dvl-ingress01 'env -u PS1 zsh -l -i -x -c exit 2>&1 | grep -nE "PS1|export|typeset"'

Most of the output was compinit noise. The useful part was this:

+/usr/home/dvl/src/scripts/bash_profile_global:16> export PS1=...
...
+/usr/home/dvl/.zshrc:42> PS1='[%F{cyan}%B%T%b%f ...'
+/usr/home/dvl/.zshrc:43> export PS2='[%F{cyan}%B%T%b%f %F{green}%B>%b%f] %F{magenta}%B'

My .zshrc sources bash_profile_global, a file I share with bash. Line 16 of that file exports a bash-style PS1. When .zshrc later assigns the zsh prompt, the variable is already exported, and it stays that way. Removing the export from .zshrc could never have been enough.

The trace also showed PS2 being exported, with the same problem.

The fix

Three lines, two files, no more export:

Index: bash_profile_global
===================================================================
-export PS1="[\[$(tput sgr0)\]\[\033[38;5;11m\]\u\[$(tput sgr0)\]@\h:..."
+PS1="[\[$(tput sgr0)\]\[\033[38;5;11m\]\u\[$(tput sgr0)\]@\h:..."
Index: zshrc
===================================================================
-export PS1="[$cTIM%T$cSTD $cSRV%m$cSTD $cUSR%n$cSTD $cPWD%~$cSTD] $cPMT%#$cSTD "
-export PS2="[$cTIM%T$cSTD $cUSR>$cSTD] $cPWD"
+PS1="[$cTIM%T$cSTD $cSRV%m$cSTD $cUSR%n$cSTD $cPWD%~$cSTD] $cPMT%#$cSTD "
+PS2="[$cTIM%T$cSTD $cUSR>$cSTD] $cPWD"

Two things caught me out on the way.

  • I edited the files on pro05 and then tested on dvl-ingress01. Each host has its own checkout.
  • sed -i refused to edit ~/.zshrc: "in-place editing only works for regular files". It’s a symlink into ~/src/scripts. Editing the real file, or using "$(realpath ~/.zshrc)", works.

Also, an existing shell keeps whatever it already inherited. After the fix, either log in again or clear it by hand:

typeset +x PS1 PS2

To confirm the fix, this should print nothing:

ssh dvl-ingress01 -t 'zsh -l -i -c "env | grep -E \"^(PS1|PS2)=\""'

It did. Now sudo jexec index gives me a plain # prompt, as it should.

One side effect: a nested bash started from a bash login shell no longer inherits my custom prompt, because bash_profile_global isn’t read by non-login shells. I can live with that. If not, ~/.bashrc can set it.

The lesson

When a variable is coming from somewhere you can’t find, zsh -x will tell you. I should have started there.

Leave a Comment

Scroll to Top