mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 09:14:38 +08:00
Harden message log maintenance tooling
This commit is contained in:
parent
8b55a3d9ab
commit
5240756f96
10 changed files with 369 additions and 34 deletions
8
Makefile
8
Makefile
|
|
@ -34,7 +34,7 @@ MANDIR ?= $(PREFIX)/share/man
|
||||||
SYSTEMD_UNIT_DIR ?= $(PREFIX)/lib/systemd/system
|
SYSTEMD_UNIT_DIR ?= $(PREFIX)/lib/systemd/system
|
||||||
CI_TEST_PORT ?= $(if $(PORT),$(PORT),2222)
|
CI_TEST_PORT ?= $(if $(PORT),$(PORT),2222)
|
||||||
|
|
||||||
.PHONY: all clean install install-systemd uninstall uninstall-systemd debug release release-check release-check-strict asan valgrind check test test-advisory ci-test unit-test integration-test anonymous-access-test connection-limit-test security-test stress-test soak-test slow-client-test user-lifecycle-test info
|
.PHONY: all clean install install-systemd uninstall uninstall-systemd debug release release-check release-check-strict asan valgrind check test test-advisory ci-test unit-test script-test integration-test anonymous-access-test connection-limit-test security-test stress-test soak-test slow-client-test user-lifecycle-test info
|
||||||
|
|
||||||
all: $(TARGETS)
|
all: $(TARGETS)
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ check:
|
||||||
@command -v clang-tidy >/dev/null 2>&1 && clang-tidy src/*.c -- -Iinclude $(INCLUDES) || echo "clang-tidy not installed"
|
@command -v clang-tidy >/dev/null 2>&1 && clang-tidy src/*.c -- -Iinclude $(INCLUDES) || echo "clang-tidy not installed"
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
test: all unit-test integration-test
|
test: all unit-test script-test integration-test
|
||||||
|
|
||||||
test-advisory: all unit-test
|
test-advisory: all unit-test
|
||||||
@echo "Running integration tests..."
|
@echo "Running integration tests..."
|
||||||
|
|
@ -120,6 +120,10 @@ unit-test:
|
||||||
@echo "Running unit tests..."
|
@echo "Running unit tests..."
|
||||||
@$(MAKE) -C tests/unit run
|
@$(MAKE) -C tests/unit run
|
||||||
|
|
||||||
|
script-test:
|
||||||
|
@echo "Running script tests..."
|
||||||
|
@cd tests && ./test_logrotate.sh
|
||||||
|
|
||||||
integration-test: all
|
integration-test: all
|
||||||
@echo "Running integration tests..."
|
@echo "Running integration tests..."
|
||||||
@cd tests && PORT=$${PORT:-2222} ./test_basic.sh
|
@cd tests && PORT=$${PORT:-2222} ./test_basic.sh
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -217,6 +217,19 @@ tntctl -p 2222 chat.example.com dump -n 100
|
||||||
tntctl -l operator chat.example.com post "service notice"
|
tntctl -l operator chat.example.com post "service notice"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Log Maintenance
|
||||||
|
|
||||||
|
Persisted public history is stored as `messages.log` in the TNT state
|
||||||
|
directory. For manual maintenance, archive and compact it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/logrotate.sh /var/lib/tnt/messages.log 100 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
The script archives the full log, keeps the last `KEEP_LINES` records in the
|
||||||
|
active file, compresses the archive when `gzip` is available, and can be
|
||||||
|
previewed with `--dry-run`.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
including parser, sanitization, and partial-record recovery rules.
|
including parser, sanitization, and partial-record recovery rules.
|
||||||
- Added `dump [N]` / `dump -n N` to the SSH exec interface and `tntctl` for
|
- Added `dump [N]` / `dump -n N` to the SSH exec interface and `tntctl` for
|
||||||
exporting valid persisted `messages.log` v1 records.
|
exporting valid persisted `messages.log` v1 records.
|
||||||
|
- Added regression-tested manual log archive and compaction coverage for
|
||||||
|
`scripts/logrotate.sh`.
|
||||||
- Added a public security policy, supported-version guidance, and GitHub issue
|
- Added a public security policy, supported-version guidance, and GitHub issue
|
||||||
templates for bug reports and feature requests.
|
templates for bug reports and feature requests.
|
||||||
- Added `tntctl`, a thin local wrapper around the documented SSH exec
|
- Added `tntctl`, a thin local wrapper around the documented SSH exec
|
||||||
|
|
@ -58,6 +60,9 @@
|
||||||
- Message-log replay and search now share one strict record parser and skip
|
- Message-log replay and search now share one strict record parser and skip
|
||||||
malformed, invalid UTF-8, extra-separator, oversized, or unterminated
|
malformed, invalid UTF-8, extra-separator, oversized, or unterminated
|
||||||
records instead of accepting partial replay data.
|
records instead of accepting partial replay data.
|
||||||
|
- `scripts/logrotate.sh` now has validated arguments, stable exit statuses,
|
||||||
|
dry-run support, archive retention, gzip-aware archives, and a regression
|
||||||
|
test in the normal test suite.
|
||||||
- The two-user lifecycle test now covers opening `:inbox` before a private
|
- The two-user lifecycle test now covers opening `:inbox` before a private
|
||||||
message arrives, matching the way users often leave an inbox page open.
|
message arrives, matching the way users often leave an inbox page open.
|
||||||
- Private-message inbox access now uses its own mutex instead of sharing the
|
- Private-message inbox access now uses its own mutex instead of sharing the
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,24 @@ sudo rm /var/lib/tnt/motd.txt
|
||||||
|
|
||||||
No restart required — TNT reads the file on each new connection.
|
No restart required — TNT reads the file on each new connection.
|
||||||
|
|
||||||
|
## Manual Log Maintenance
|
||||||
|
|
||||||
|
TNT stores public chat history in `messages.log` under the state directory.
|
||||||
|
Use the maintenance script from a source checkout when the service is stopped
|
||||||
|
or during a quiet maintenance window:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop tnt
|
||||||
|
sudo scripts/logrotate.sh /var/lib/tnt/messages.log 100 10000
|
||||||
|
sudo systemctl start tnt
|
||||||
|
```
|
||||||
|
|
||||||
|
The arguments are `LOG_FILE MAX_SIZE_MB KEEP_LINES`. The script archives the
|
||||||
|
full log, compacts the active log to the last `KEEP_LINES` records, compresses
|
||||||
|
the archive when `gzip` is available, and keeps the newest five archives by
|
||||||
|
default. Use `--dry-run` to preview actions, or `--keep-archives N` to change
|
||||||
|
archive retention.
|
||||||
|
|
||||||
## Firewall
|
## Firewall
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,21 @@ interface and `tntctl`. The output format is exactly the v1 record format
|
||||||
above. Without `N`, `dump` exports all valid records; with `N`, it exports the
|
above. Without `N`, `dump` exports all valid records; with `N`, it exports the
|
||||||
last `N` valid records.
|
last `N` valid records.
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
`scripts/logrotate.sh` is the manual archive and compaction tool for
|
||||||
|
`messages.log`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/logrotate.sh [--dry-run] [--keep-archives N] LOG_FILE MAX_SIZE_MB KEEP_LINES
|
||||||
|
```
|
||||||
|
|
||||||
|
When the log exceeds `MAX_SIZE_MB`, the script archives the full file, compacts
|
||||||
|
the active file to the last `KEEP_LINES` records, compresses the archive when
|
||||||
|
`gzip` is available, and removes older archives beyond the retention limit.
|
||||||
|
Run it while TNT is stopped or during a quiet maintenance window if strict log
|
||||||
|
consistency matters.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
The v1 record format is stable for TNT 1.x. Future incompatible storage
|
The v1 record format is stable for TNT 1.x. Future incompatible storage
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ EXEC COMMANDS
|
||||||
dump [N] / dump -n N persisted messages.log v1 records
|
dump [N] / dump -n N persisted messages.log v1 records
|
||||||
post <message> post as the SSH login name
|
post <message> post as the SSH login name
|
||||||
|
|
||||||
|
MAINTENANCE
|
||||||
|
scripts/logrotate.sh LOG_FILE MAX_SIZE_MB KEEP_LINES
|
||||||
|
archive and compact messages.log
|
||||||
|
scripts/logrotate.sh --dry-run ...
|
||||||
|
preview log maintenance actions
|
||||||
|
|
||||||
STRUCTURE
|
STRUCTURE
|
||||||
src/main.c entry, signals
|
src/main.c entry, signals
|
||||||
src/cli_text.c startup CLI text
|
src/cli_text.c startup CLI text
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ Goal: make stored history durable, inspectable, and recoverable.
|
||||||
- ✅ keep persisted timestamps in UTC throughout write and replay
|
- ✅ keep persisted timestamps in UTC throughout write and replay
|
||||||
- ✅ validate persisted UTF-8 and record structure before replay/search
|
- ✅ validate persisted UTF-8 and record structure before replay/search
|
||||||
- ✅ provide an inspection/export command for persisted records
|
- ✅ provide an inspection/export command for persisted records
|
||||||
- add log rotation and compaction tooling
|
- ✅ add log rotation and compaction tooling
|
||||||
- define broader recovery tooling for truncated or partially corrupted logs
|
- define broader recovery tooling for truncated or partially corrupted logs
|
||||||
|
|
||||||
## Stage 4: Interactive UX
|
## Stage 4: Interactive UX
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,174 @@
|
||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
# TNT Log Rotation Script
|
# Compact and archive a TNT messages.log file.
|
||||||
# Keeps chat history manageable and prevents disk space issues
|
#
|
||||||
|
# This is an operator-run maintenance tool. For strict consistency, stop TNT
|
||||||
|
# or run it during a quiet maintenance window before compacting the active log.
|
||||||
|
|
||||||
LOG_FILE="${1:-/var/lib/tnt/messages.log}"
|
set -eu
|
||||||
MAX_SIZE_MB="${2:-100}"
|
|
||||||
KEEP_LINES="${3:-10000}"
|
|
||||||
|
|
||||||
# Check if log file exists
|
DRY_RUN=0
|
||||||
if [ ! -f "$LOG_FILE" ]; then
|
KEEP_ARCHIVES=5
|
||||||
echo "Log file $LOG_FILE does not exist"
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: scripts/logrotate.sh [--dry-run] [--keep-archives N] [LOG_FILE [MAX_SIZE_MB [KEEP_LINES]]]
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
LOG_FILE /var/lib/tnt/messages.log
|
||||||
|
MAX_SIZE_MB 100
|
||||||
|
KEEP_LINES 10000
|
||||||
|
|
||||||
|
Exit status:
|
||||||
|
0 success, including missing log file
|
||||||
|
1 runtime error
|
||||||
|
64 invalid arguments
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_usage() {
|
||||||
|
echo "logrotate: $*" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 64
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "logrotate: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_uint() {
|
||||||
|
case "${1:-}" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_positive_uint() {
|
||||||
|
is_uint "$1" && [ "$1" -gt 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--keep-archives)
|
||||||
|
[ "$#" -ge 2 ] || fail_usage "missing value for --keep-archives"
|
||||||
|
is_uint "$2" || fail_usage "invalid archive count: $2"
|
||||||
|
KEEP_ARCHIVES=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
fail_usage "unknown option: $1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$#" -le 3 ] || fail_usage "too many arguments"
|
||||||
|
|
||||||
|
LOG_FILE=${1:-/var/lib/tnt/messages.log}
|
||||||
|
MAX_SIZE_MB=${2:-100}
|
||||||
|
KEEP_LINES=${3:-10000}
|
||||||
|
|
||||||
|
case "$LOG_FILE" in
|
||||||
|
''|-*)
|
||||||
|
fail_usage "invalid log path"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
is_uint "$MAX_SIZE_MB" || fail_usage "invalid max size: $MAX_SIZE_MB"
|
||||||
|
is_positive_uint "$KEEP_LINES" || fail_usage "invalid keep lines: $KEEP_LINES"
|
||||||
|
|
||||||
|
if [ ! -e "$LOG_FILE" ]; then
|
||||||
|
echo "logrotate: $LOG_FILE does not exist"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
[ -f "$LOG_FILE" ] || fail "$LOG_FILE is not a regular file"
|
||||||
|
|
||||||
# Get file size in MB
|
MAX_BYTES=$((MAX_SIZE_MB * 1024 * 1024))
|
||||||
FILE_SIZE=$(du -m "$LOG_FILE" | cut -f1)
|
FILE_SIZE=$(wc -c < "$LOG_FILE" | tr -d ' ')
|
||||||
|
[ -n "$FILE_SIZE" ] || fail "could not read log size"
|
||||||
|
|
||||||
# Rotate if file is too large
|
compact_log() {
|
||||||
if [ "$FILE_SIZE" -gt "$MAX_SIZE_MB" ]; then
|
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
echo "Log file size: ${FILE_SIZE}MB, rotating..."
|
backup="${LOG_FILE}.${timestamp}"
|
||||||
|
suffix=1
|
||||||
|
|
||||||
# Create backup
|
while [ -e "$backup" ] || [ -e "${backup}.gz" ]; do
|
||||||
BACKUP="${LOG_FILE}.$(date +%Y%m%d_%H%M%S)"
|
backup="${LOG_FILE}.${timestamp}.${suffix}"
|
||||||
cp "$LOG_FILE" "$BACKUP"
|
suffix=$((suffix + 1))
|
||||||
|
done
|
||||||
|
|
||||||
# Keep only last N lines
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
tail -n "$KEEP_LINES" "$LOG_FILE" > "${LOG_FILE}.tmp"
|
echo "logrotate: would archive $LOG_FILE to $backup"
|
||||||
mv "${LOG_FILE}.tmp" "$LOG_FILE"
|
echo "logrotate: would keep last $KEEP_LINES lines"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Compress old backup
|
tmp="${LOG_FILE}.tmp.$$"
|
||||||
gzip "$BACKUP"
|
rm -f "$tmp"
|
||||||
|
cp -p "$LOG_FILE" "$backup" || fail "failed to create archive"
|
||||||
|
if ! tail -n "$KEEP_LINES" "$LOG_FILE" > "$tmp"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
fail "failed to compact log"
|
||||||
|
fi
|
||||||
|
if ! cat "$tmp" > "$LOG_FILE"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
fail "failed to replace log"
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
|
||||||
echo "Log rotated. Backup: ${BACKUP}.gz"
|
if command -v gzip >/dev/null 2>&1; then
|
||||||
echo "Kept last $KEEP_LINES lines"
|
gzip -f "$backup" || fail "failed to compress archive"
|
||||||
|
backup="${backup}.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "logrotate: archived $backup"
|
||||||
|
echo "logrotate: kept last $KEEP_LINES lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_archives() {
|
||||||
|
[ "$KEEP_ARCHIVES" -ge 0 ] || return 0
|
||||||
|
|
||||||
|
archives=$(
|
||||||
|
ls -1t "$LOG_FILE".*.gz "$LOG_FILE".[0-9]* 2>/dev/null || true
|
||||||
|
)
|
||||||
|
[ -n "$archives" ] || return 0
|
||||||
|
|
||||||
|
printf '%s\n' "$archives" |
|
||||||
|
awk '!seen[$0]++' |
|
||||||
|
awk -v keep="$KEEP_ARCHIVES" 'NR > keep' |
|
||||||
|
while IFS= read -r old; do
|
||||||
|
[ -n "$old" ] || continue
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
|
echo "logrotate: would remove $old"
|
||||||
|
else
|
||||||
|
rm -f "$old"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$FILE_SIZE" -gt "$MAX_BYTES" ]; then
|
||||||
|
echo "logrotate: size ${FILE_SIZE} bytes exceeds ${MAX_BYTES} bytes"
|
||||||
|
compact_log
|
||||||
else
|
else
|
||||||
echo "Log file size: ${FILE_SIZE}MB (under ${MAX_SIZE_MB}MB limit)"
|
echo "logrotate: size ${FILE_SIZE} bytes is within ${MAX_BYTES} bytes"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up old compressed logs (keep last 5)
|
cleanup_archives
|
||||||
LOG_DIR=$(dirname "$LOG_FILE")
|
echo "logrotate: complete"
|
||||||
cd "$LOG_DIR" || exit
|
|
||||||
ls -t messages.log.*.gz 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null
|
|
||||||
|
|
||||||
echo "Log rotation complete"
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ Default checks:
|
||||||
- version metadata alignment
|
- version metadata alignment
|
||||||
- clean build
|
- clean build
|
||||||
- unit tests
|
- unit tests
|
||||||
|
- script tests
|
||||||
- staged install layout with PREFIX=/usr and DESTDIR
|
- staged install layout with PREFIX=/usr and DESTDIR
|
||||||
- installer shell syntax
|
- installer shell syntax
|
||||||
- Debian packaging metadata
|
- Debian packaging metadata
|
||||||
|
|
@ -102,6 +103,9 @@ step "running unit tests"
|
||||||
make -C tests/unit clean
|
make -C tests/unit clean
|
||||||
make -C tests/unit run
|
make -C tests/unit run
|
||||||
|
|
||||||
|
step "running script tests"
|
||||||
|
make script-test
|
||||||
|
|
||||||
step "checking client I/O ownership boundaries"
|
step "checking client I/O ownership boundaries"
|
||||||
! grep -R "client_send(target" src include >/dev/null ||
|
! grep -R "client_send(target" src include >/dev/null ||
|
||||||
fail "cross-client target writes must be queued through client_queue_bell"
|
fail "cross-client target writes must be queued through client_queue_bell"
|
||||||
|
|
|
||||||
140
tests/test_logrotate.sh
Executable file
140
tests/test_logrotate.sh
Executable file
|
|
@ -0,0 +1,140 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Maintenance-script regression tests for scripts/logrotate.sh.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
SCRIPT="../scripts/logrotate.sh"
|
||||||
|
STATE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tnt-logrotate-test.XXXXXX")
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$STATE_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
echo "✓ $1"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "✗ $1"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_payload() {
|
||||||
|
archive=$1
|
||||||
|
case "$archive" in
|
||||||
|
*.gz) gzip -cd "$archive" ;;
|
||||||
|
*) cat "$archive" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== TNT Logrotate Tests ==="
|
||||||
|
|
||||||
|
if [ ! -x "$SCRIPT" ]; then
|
||||||
|
echo "Error: script $SCRIPT not found or not executable."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MISSING_OUTPUT=$("$SCRIPT" "$STATE_DIR/missing.log" 100 10 2>&1)
|
||||||
|
MISSING_STATUS=$?
|
||||||
|
printf '%s\n' "$MISSING_OUTPUT" | grep -q 'does not exist'
|
||||||
|
if [ "$MISSING_STATUS" -eq 0 ] && [ $? -eq 0 ]; then
|
||||||
|
pass "missing log is a successful no-op"
|
||||||
|
else
|
||||||
|
fail "missing log handling"
|
||||||
|
printf '%s\n' "$MISSING_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOG="$STATE_DIR/messages.log"
|
||||||
|
cat > "$LOG" <<'EOF'
|
||||||
|
2026-01-01T00:00:01Z|alice|one
|
||||||
|
2026-01-01T00:00:02Z|bob|two
|
||||||
|
2026-01-01T00:00:03Z|carol|three
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if "$SCRIPT" "$LOG" 100 2 >/dev/null 2>&1 &&
|
||||||
|
grep -q 'alice|one' "$LOG" &&
|
||||||
|
[ "$(ls "$LOG".* 2>/dev/null | wc -l | tr -d ' ')" -eq 0 ]; then
|
||||||
|
pass "small log stays unmodified"
|
||||||
|
else
|
||||||
|
fail "small log no-op"
|
||||||
|
cat "$LOG" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROTATE_OUTPUT=$("$SCRIPT" "$LOG" 0 2 2>&1)
|
||||||
|
ROTATE_STATUS=$?
|
||||||
|
ARCHIVE=$(ls "$LOG".*.gz "$LOG".[0-9]* 2>/dev/null | head -n 1)
|
||||||
|
if [ "$ROTATE_STATUS" -eq 0 ] &&
|
||||||
|
printf '%s\n' "$ROTATE_OUTPUT" | grep -q 'kept last 2 lines' &&
|
||||||
|
! grep -q 'alice|one' "$LOG" &&
|
||||||
|
grep -q 'bob|two' "$LOG" &&
|
||||||
|
grep -q 'carol|three' "$LOG" &&
|
||||||
|
[ -n "$ARCHIVE" ] &&
|
||||||
|
archive_payload "$ARCHIVE" | grep -q 'alice|one'; then
|
||||||
|
pass "oversize log is archived and compacted"
|
||||||
|
else
|
||||||
|
fail "oversize rotation"
|
||||||
|
printf '%s\n' "$ROTATE_OUTPUT"
|
||||||
|
cat "$LOG" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
DRY_LOG="$STATE_DIR/dry.log"
|
||||||
|
printf 'line1\nline2\nline3\n' > "$DRY_LOG"
|
||||||
|
DRY_BEFORE=$(cat "$DRY_LOG")
|
||||||
|
DRY_OUTPUT=$("$SCRIPT" --dry-run "$DRY_LOG" 0 1 2>&1)
|
||||||
|
DRY_STATUS=$?
|
||||||
|
if [ "$DRY_STATUS" -eq 0 ] &&
|
||||||
|
[ "$(cat "$DRY_LOG")" = "$DRY_BEFORE" ] &&
|
||||||
|
printf '%s\n' "$DRY_OUTPUT" | grep -q 'would archive'; then
|
||||||
|
pass "dry run does not modify the log"
|
||||||
|
else
|
||||||
|
fail "dry run handling"
|
||||||
|
printf '%s\n' "$DRY_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INVALID_OUTPUT=$("$SCRIPT" "$LOG" nope 2 2>&1)
|
||||||
|
INVALID_STATUS=$?
|
||||||
|
if [ "$INVALID_STATUS" -eq 64 ] &&
|
||||||
|
printf '%s\n' "$INVALID_OUTPUT" | grep -q 'invalid max size'; then
|
||||||
|
pass "invalid arguments exit 64"
|
||||||
|
else
|
||||||
|
fail "invalid argument status"
|
||||||
|
printf '%s\n' "$INVALID_OUTPUT"
|
||||||
|
echo "exit status: $INVALID_STATUS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DIR_OUTPUT=$("$SCRIPT" "$STATE_DIR" 0 1 2>&1)
|
||||||
|
DIR_STATUS=$?
|
||||||
|
if [ "$DIR_STATUS" -eq 1 ] &&
|
||||||
|
printf '%s\n' "$DIR_OUTPUT" | grep -q 'not a regular file'; then
|
||||||
|
pass "non-regular log path is rejected"
|
||||||
|
else
|
||||||
|
fail "non-regular path handling"
|
||||||
|
printf '%s\n' "$DIR_OUTPUT"
|
||||||
|
echo "exit status: $DIR_STATUS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RET_LOG="$STATE_DIR/retention.log"
|
||||||
|
printf 'a\nb\nc\n' > "$RET_LOG"
|
||||||
|
printf old1 > "$RET_LOG.20000101T000000Z.gz"
|
||||||
|
sleep 1
|
||||||
|
printf old2 > "$RET_LOG.20010101T000000Z.gz"
|
||||||
|
sleep 1
|
||||||
|
printf old3 > "$RET_LOG.20020101T000000Z.gz"
|
||||||
|
|
||||||
|
if "$SCRIPT" --keep-archives 2 "$RET_LOG" 100 2 >/dev/null 2>&1 &&
|
||||||
|
[ "$(ls "$RET_LOG".*.gz 2>/dev/null | wc -l | tr -d ' ')" -eq 2 ]; then
|
||||||
|
pass "archive retention removes older archives"
|
||||||
|
else
|
||||||
|
fail "archive retention"
|
||||||
|
ls "$RET_LOG".* 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "PASSED: $PASS"
|
||||||
|
echo "FAILED: $FAIL"
|
||||||
|
[ "$FAIL" -eq 0 ] && echo "All tests passed" || echo "Some tests failed"
|
||||||
|
exit "$FAIL"
|
||||||
Loading…
Reference in a new issue