set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
if [[ -x .venv/bin/zensical ]]; then
ZENSICAL=.venv/bin/zensical
elif command -v zensical >/dev/null 2>&1; then
ZENSICAL=zensical
else
cat >&2 <<'EOF'
zensical is not installed. Set up the docs venv once:
python3 -m venv .venv
./.venv/bin/pip install -r requirements.txt
then re-run s/docs.
EOF
exit 1
fi
if [[ -r /proc/sys/fs/inotify/max_user_instances ]] && command -v find >/dev/null 2>&1; then
limit=$(cat /proc/sys/fs/inotify/max_user_instances 2>/dev/null || echo 0)
held=$(find /proc -maxdepth 3 -user "$(id -u)" -name fd 2>/dev/null \
| xargs -r -I{} ls -l {} 2>/dev/null \
| grep -c 'anon_inode:inotify' || true)
if [[ "$limit" -gt 0 && "$held" -ge "$limit" ]]; then
cat >&2 <<EOF
ERROR: no free inotify instances for Zensical's file watcher
cap: fs.inotify.max_user_instances = $limit
held: $held (by existing processes in your user session)
Fix this session:
sudo sysctl fs.inotify.max_user_instances=$(( limit * 2 < 512 ? 512 : limit * 2 ))
Persist across reboots:
echo 'fs.inotify.max_user_instances=512' | sudo tee /etc/sysctl.d/99-inotify.conf
sudo sysctl --system
Then re-run s/docs.
EOF
exit 1
fi
if [[ "$limit" -gt 0 && "$held" -ge $(( limit - 4 )) ]]; then
echo >&2 "warning: only $(( limit - held )) free inotify instances (cap $limit, held $held)"
echo >&2 " if Zensical panics with \"Too many open files\", raise the cap:"
echo >&2 " sudo sysctl fs.inotify.max_user_instances=$(( limit * 2 ))"
echo >&2
fi
fi
PORT_MIN=8000
PORT_MAX=8030
addr_given=0
open_given=0
for arg in "$@"; do
case "$arg" in
-a | --dev-addr | --dev-addr=*) addr_given=1 ;;
--open) open_given=1 ;;
esac
done
port_in_use() {
if command -v ss >/dev/null 2>&1; then
ss -ltnH 2>/dev/null | awk '{print $4}' | awk -F: '{print $NF}' | grep -qx "$1"
elif command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1
else
(exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null
fi
}
if [[ "$addr_given" -eq 0 ]]; then
chosen=""
for port in $(seq "$PORT_MIN" "$PORT_MAX"); do
if ! port_in_use "$port"; then
chosen="$port"
break
fi
done
if [[ -n "$chosen" ]]; then
echo >&2 "Serving docs on http://localhost:$chosen"
set -- --dev-addr "localhost:$chosen" "$@"
else
echo >&2 "warning: no free port in ${PORT_MIN}-${PORT_MAX}; letting zensical use its default"
fi
fi
if [[ "$open_given" -eq 0 ]]; then
set -- --open "$@"
fi
exec "$ZENSICAL" serve "$@"