-
Notifications
You must be signed in to change notification settings - Fork 7
Add BrainLayer newsyslog rotation #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | ||
| SRC="$SCRIPT_DIR/newsyslog.d/brainlayer.conf" | ||
| DST="/etc/newsyslog.d/brainlayer.conf" | ||
| OWNER="${BRAINLAYER_LOG_OWNER:-${SUDO_USER:-$(id -un)}}" | ||
| GROUP="${BRAINLAYER_LOG_GROUP:-staff}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GROUP variable not validated for whitespace like LOG_DIRLow Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit a0cbf99. Configure here. |
||
| RENDERED_CONFIG="$(mktemp "${TMPDIR:-/tmp}/brainlayer-newsyslog.XXXXXX")" | ||
| trap 'rm -f "$RENDERED_CONFIG"' EXIT | ||
|
|
||
| escape_sed_replacement() { | ||
| printf '%s' "$1" | sed 's/[\/#&\\]/\\&/g' | ||
| } | ||
|
|
||
| if ! id -u "$OWNER" >/dev/null 2>&1; then | ||
| echo "ERROR: log owner does not exist: $OWNER" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! OWNER_HOME="$(dscl . -read "/Users/$OWNER" NFSHomeDirectory 2>/dev/null | sed 's/^NFSHomeDirectory:[[:space:]]*//')"; then | ||
| echo "ERROR: could not resolve home directory for $OWNER" >&2 | ||
| exit 1 | ||
| fi | ||
| if [ -z "$OWNER_HOME" ]; then | ||
| echo "ERROR: could not resolve home directory for $OWNER" >&2 | ||
| exit 1 | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| fi | ||
| LOG_DIR="${BRAINLAYER_LOG_DIR:-$OWNER_HOME/Library/Logs/brainlayer}" | ||
| if [[ "$LOG_DIR" =~ [[:space:]] ]]; then | ||
| echo "ERROR: newsyslog log paths cannot contain whitespace: $LOG_DIR" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ ! -f "$SRC" ]; then | ||
| echo "ERROR: $SRC not found" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| LOG_DIR_ESCAPED="$(escape_sed_replacement "$LOG_DIR")" | ||
| OWNER_GROUP_ESCAPED="$(escape_sed_replacement "$OWNER:$GROUP")" | ||
|
|
||
| sed \ | ||
| -e "s#/Users/etanheyman/Library/Logs/brainlayer#$LOG_DIR_ESCAPED#g" \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the invoking user's home or Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a958d49. The installer now rejects whitespace-containing LOG_DIR values before rendering because newsyslog.conf is whitespace-delimited; README and tests document that explicit limitation. |
||
| -e "s#etanheyman:staff#$OWNER_GROUP_ESCAPED#g" \ | ||
| "$SRC" >"$RENDERED_CONFIG" | ||
|
|
||
| sudo mkdir -p "$LOG_DIR" | ||
| # Ensure already-created logs are writable by user LaunchAgents before the first rotation. | ||
| sudo chown "$OWNER:$GROUP" "$LOG_DIR" | ||
| for log in "$LOG_DIR"/*.log; do | ||
| [ -e "$log" ] || continue | ||
| if [ -L "$log" ] || [ ! -f "$log" ]; then | ||
| echo "Skipping non-regular log path: $log" >&2 | ||
| continue | ||
| fi | ||
| sudo chown "$OWNER:$GROUP" "$log" | ||
|
Comment on lines
+51
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this installer is run by an admin for a Useful? React with 👍 / 👎.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a958d49. The ownership repair loop now skips symlinks and non-regular paths before any sudo chown/chmod, so user-controlled log symlinks are not followed. |
||
| sudo chmod 0644 "$log" | ||
| done | ||
|
macroscopeapp[bot] marked this conversation as resolved.
|
||
|
|
||
| sudo newsyslog -nv -f "$RENDERED_CONFIG" | ||
| sudo mkdir -p /etc/newsyslog.d | ||
| sudo install -o root -g wheel -m 0644 "$RENDERED_CONFIG" "$DST" | ||
| sudo newsyslog -nv -f "$DST" | ||
| echo "Installed $DST" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # BrainLayer newsyslog | ||
|
|
||
| BrainLayer LaunchAgents write logs as the user, not as root. macOS `newsyslog` | ||
| creates rotated replacement files as `root:admin` unless the config line | ||
| specifies an owner and group. A root-owned replacement silently breaks later | ||
| appends from user-level daemons. | ||
|
|
||
| This drop-in only rotates finite scheduled LaunchAgent jobs. Long-running jobs | ||
| such as BrainBar, watch, and enrichment keep their `StandardOutPath` and | ||
| `StandardErrorPath` descriptors open; macOS `newsyslog` has no post-rotate hook | ||
| or copy-truncate mode, so those logs need a coupled launchd restart or pid-file | ||
| signal path before they can be safely added. Drain is also excluded because it | ||
| can be spawned while a rotation pass is running. | ||
|
|
||
| Install `brainlayer.conf` into `/etc/newsyslog.d/` with: | ||
|
|
||
| ```sh | ||
| launchd/install-newsyslog.sh | ||
| sudo newsyslog -nv -f /etc/newsyslog.d/brainlayer.conf | ||
| ``` | ||
|
|
||
| The checked-in config targets Etan's LaunchAgent account as `etanheyman:staff`. | ||
| The installer renders that config for `BRAINLAYER_LOG_OWNER`, | ||
| `BRAINLAYER_LOG_GROUP`, and `BRAINLAYER_LOG_DIR` before installing it into | ||
| `/etc/newsyslog.d/`. The rendered config is validated with `newsyslog -nv` | ||
| before replacing the live drop-in. Because `newsyslog.conf` is | ||
| whitespace-delimited, the installer rejects log directories containing | ||
| whitespace. | ||
|
|
||
| Every installed entry uses mode `644`, `J` compression, and `N` so rotation does | ||
| not signal user LaunchAgents. Launchd owns job lifecycle. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # BrainLayer finite user LaunchAgent logs. | ||
| # owner:group is explicit so rotated files remain writable by user-level daemons. | ||
| # Long-running StandardOutPath/StandardErrorPath jobs must not be added without | ||
| # a coupled launchd restart or pid-file signal path; newsyslog has no | ||
| # post-rotate hook or copy-truncate mode on macOS. | ||
| # logfilename owner:group mode count size when flags | ||
| /Users/etanheyman/Library/Logs/brainlayer/backup-daily.out.log etanheyman:staff 644 4 512 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/backup-daily.err.log etanheyman:staff 644 4 512 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/decay.out.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/decay.err.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/wal-checkpoint.out.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/wal-checkpoint.err.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/index.out.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/index.err.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/repair-fts.out.log etanheyman:staff 644 7 1024 * JN | ||
| /Users/etanheyman/Library/Logs/brainlayer/repair-fts.err.log etanheyman:staff 644 7 1024 * JN |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parents[1] | ||
| CONFIG = REPO_ROOT / "launchd" / "newsyslog.d" / "brainlayer.conf" | ||
| INSTALLER = REPO_ROOT / "launchd" / "install-newsyslog.sh" | ||
|
|
||
|
|
||
| def _entries() -> list[list[str]]: | ||
| return [ | ||
| line.split() for line in CONFIG.read_text().splitlines() if line.strip() and not line.lstrip().startswith("#") | ||
| ] | ||
|
|
||
|
|
||
| def test_newsyslog_config_uses_user_writable_rotated_logs(): | ||
| entries = _entries() | ||
| assert entries | ||
| for fields in entries: | ||
| assert fields[0].startswith("/Users/etanheyman/Library/Logs/brainlayer/") | ||
| assert fields[0].endswith(".log") | ||
| assert fields[1] == "etanheyman:staff" | ||
| assert fields[2] == "644" | ||
| assert fields[6] == "JN" | ||
|
|
||
|
|
||
| def test_newsyslog_config_covers_launchd_log_pairs(): | ||
| names = {Path(fields[0]).name for fields in _entries()} | ||
| for daemon in { | ||
| "backup-daily", | ||
| "decay", | ||
| "wal-checkpoint", | ||
| "index", | ||
| "repair-fts", | ||
| }: | ||
| assert f"{daemon}.out.log" in names | ||
| assert f"{daemon}.err.log" in names | ||
|
|
||
|
|
||
| def test_newsyslog_config_excludes_held_open_long_running_launchd_logs(): | ||
| names = {Path(fields[0]).name for fields in _entries()} | ||
| for daemon in {"brainbar", "enrichment", "watch", "drain"}: | ||
| assert f"{daemon}.out.log" not in names | ||
| assert f"{daemon}.err.log" not in names | ||
|
|
||
| config = CONFIG.read_text() | ||
| assert "post-rotate hook or copy-truncate mode" in config | ||
|
|
||
|
|
||
| def test_newsyslog_installer_documents_root_owned_replacement_footgun(): | ||
| readme = (REPO_ROOT / "launchd" / "newsyslog.d" / "README.md").read_text() | ||
| assert "root:admin" in readme | ||
| assert "etanheyman:staff" in readme | ||
| assert "Long-running jobs" in readme | ||
| assert "post-rotate hook" in readme | ||
| assert "sudo newsyslog -nv -f /etc/newsyslog.d/brainlayer.conf" in readme | ||
|
|
||
|
|
||
| def test_newsyslog_installer_repairs_root_owned_logs_for_invoking_user(): | ||
| script = INSTALLER.read_text() | ||
| assert 'OWNER="${BRAINLAYER_LOG_OWNER:-${SUDO_USER:-$(id -un)}}"' in script | ||
| assert 'sudo mkdir -p "$LOG_DIR"' in script | ||
| assert '[ -L "$log" ] || [ ! -f "$log" ]' in script | ||
| assert "Skipping non-regular log path: $log" in script | ||
| assert 'sudo chown "$OWNER:$GROUP" "$log"' in script | ||
| assert 'sudo chmod 0644 "$log"' in script | ||
|
|
||
|
|
||
| def test_newsyslog_installer_renders_runtime_owner_and_log_dir(): | ||
| script = INSTALLER.read_text() | ||
| assert 'mktemp "${TMPDIR:-/tmp}/brainlayer-newsyslog.XXXXXX"' in script | ||
| assert "trap 'rm -f \"$RENDERED_CONFIG\"' EXIT" in script | ||
| assert 'LOG_DIR_ESCAPED="$(escape_sed_replacement "$LOG_DIR")"' in script | ||
| assert 'OWNER_GROUP_ESCAPED="$(escape_sed_replacement "$OWNER:$GROUP")"' in script | ||
| assert "s#/Users/etanheyman/Library/Logs/brainlayer#$LOG_DIR_ESCAPED#g" in script | ||
| assert "s#etanheyman:staff#$OWNER_GROUP_ESCAPED#g" in script | ||
| assert 'sudo newsyslog -nv -f "$RENDERED_CONFIG"' in script | ||
| assert 'sudo install -o root -g wheel -m 0644 "$RENDERED_CONFIG" "$DST"' in script | ||
|
|
||
|
|
||
| def test_newsyslog_installer_handles_home_paths_with_spaces_explicitly(): | ||
| script = INSTALLER.read_text() | ||
| assert "awk '{print $2}'" not in script | ||
| assert 'if ! OWNER_HOME="$(dscl . -read "/Users/$OWNER" NFSHomeDirectory' in script | ||
| assert "sed 's/^NFSHomeDirectory:[[:space:]]*//'" in script | ||
| assert '[[ "$LOG_DIR" =~ [[:space:]] ]]' in script | ||
| assert "newsyslog log paths cannot contain whitespace" in script |


Uh oh!
There was an error while loading. Please reload this page.