-
Notifications
You must be signed in to change notification settings - Fork 0
[codex] tighten data rotation, cleanup, and service lifecycle defaults #72
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
83be5cd
38a2dc9
fe503e2
61066f9
097e3ff
3e900d3
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,7 @@ | ||
| # Source Documentation Index | ||
|
|
||
| This index links repository source documentation that is required by the local | ||
| module documentation policy. | ||
|
|
||
| - [../tests/MODULE.md](../tests/MODULE.md) - test-suite contracts and cleanup | ||
| safety expectations. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,14 @@ | ||
| hooks: | ||
| enabled_groups: | ||
| - format | ||
| - syntax | ||
| - workflow | ||
| - go | ||
| - ai | ||
| - commit-msg | ||
|
|
||
| python: | ||
| docstring_coverage: | ||
| enabled: false | ||
| pytest_gate: | ||
| enabled: false |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| #!/usr/bin/env bash | ||
| # SPDX-FileCopyrightText: 2026 Blackcat Informatics® Inc. | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| # shellcheck shell=bash | ||
| set -euo pipefail | ||
|
|
||
| APPARMOR_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) | ||
| # shellcheck source=common.sh | ||
| # shellcheck disable=SC1091 | ||
| source "${APPARMOR_LIB_DIR}/common.sh" | ||
|
|
||
| cmd_apparmor_load() { | ||
| local parser=${APPARMOR_PARSER:-apparmor_parser} | ||
| if ! command -v "${parser}" >/dev/null 2>&1; then | ||
| echo "[apparmor] ${parser} not found. Install apparmor-utils (Debian/Ubuntu) or ensure apparmor_parser is on PATH." >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ $EUID -ne 0 ]] && ! command -v sudo >/dev/null 2>&1; then | ||
| echo "[apparmor] sudo required to load profiles or rerun as root." >&2 | ||
| exit 1 | ||
| fi | ||
| local loaded=false | ||
| for profile in "${ROOT_DIR}/apparmor"/*.profile; do | ||
| [[ -e "${profile}" ]] || continue | ||
| if [[ $EUID -ne 0 ]]; then | ||
| sudo "${parser}" -r -W "${profile}" || exit 1 | ||
| else | ||
| "${parser}" -r -W "${profile}" || exit 1 | ||
| fi | ||
| loaded=true | ||
| echo "[apparmor] loaded ${profile##*/}" >&2 | ||
| done | ||
| if [[ ${loaded} == false ]]; then | ||
| echo "[apparmor] no profiles found under ${ROOT_DIR}/apparmor" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "[apparmor] profiles loaded. Set CORE_DATA_APPARMOR_<SERVICE>=apparmor:core_data_minimal (or your custom profile) before composing." >&2 | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,195 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # SPDX-FileCopyrightText: 2026 Blackcat Informatics® Inc. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # shellcheck shell=bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DATA_CLEANUP_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # shellcheck source=common.sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # shellcheck disable=SC1091 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source "${DATA_CLEANUP_LIB_DIR}/common.sh" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DATA_CLEANUP_DEFAULT_RETENTION=${DATA_CLEANUP_DEFAULT_RETENTION:-7d} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CORE_DATA_DATA_ROOT=${CORE_DATA_DATA_ROOT:-${ROOT_DIR}/data} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_usage() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat <<'USAGE' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Usage: manage.sh data-cleanup [options] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Remove stale pytest data stashes left under data/.pytest_backups. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Options: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --older-than AGE Retain entries newer than AGE (default: 7d). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AGE accepts s, m, h, or d suffixes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --execute Delete matching entries. Without this, only report. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --force Allow execution even when compose containers are running. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --json Emit a JSON summary. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -h, --help Show this help. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| USAGE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_parse_age() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local age=$1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local suffix | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${age}" =~ ^([0-9]+)([smhd])$ ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| number=${BASH_REMATCH[1]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| suffix=${BASH_REMATCH[2]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [[ "${age}" =~ ^([0-9]+)$ ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| number=${BASH_REMATCH[1]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| suffix=d | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[data-cleanup] invalid age '${age}'; expected values like 24h or 7d." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "${suffix}" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s) echo "${number}" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| m) echo $((number * 60)) ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h) echo $((number * 60 * 60)) ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| d) echo $((number * 24 * 60 * 60)) ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_compose_running() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! output=$(compose ps -q 2>/dev/null); then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -n "${output}" ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_json_escape() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local value=$1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value=${value//\\/\\\\} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value=${value//\"/\\\"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value=${value//$'\n'/\\n} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s' "${value}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd_data_cleanup() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local older_than=${DATA_CLEANUP_DEFAULT_RETENTION} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local execute=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local force=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local json=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while [[ $# -gt 0 ]]; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --older-than) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ $# -lt 2 ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[data-cleanup] --older-than requires an age value." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| older_than=$2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --older-than=*) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| older_than=${1#*=} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --execute) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| execute=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --force) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| force=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --json) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -h | --help) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_usage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[data-cleanup] unknown option: $1" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_usage >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local retention_seconds | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retention_seconds=$(data_cleanup_parse_age "${older_than}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local now | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| now=$(date +%s) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local cutoff=$((now - retention_seconds)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local backup_root="${CORE_DATA_DATA_ROOT%/}/.pytest_backups" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local candidates=() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local candidate_count=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local total_bytes=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -d "${backup_root}" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r -d '' path; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local modified | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modified=$(stat -c '%Y' "${path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
# Search for platform detection patterns or stat usage
rg -nP '\bstat\s+-[cf]' --type=sh -C3
rg -nP '\buname\b.*Darwin|OSTYPE.*darwin' --type=sh -C3Repository: paudley/core_data Length of output: 1084 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Show the relevant section of data_cleanup.sh around the reported lines
sed -n '110,170p' scripts/lib/data_cleanup.sh | nl -ba | sed -n '1,120p'
# Find other GNU-specific du/stat usages and macOS fallbacks
rg -n --fixed-string "du -s -B" --type=sh
rg -n --fixed-string "stat -f '%m'" --type=sh
rg -nP "\bstat\s+-c\s+['\"]?%Y['\"]?" --type=sh
rg -nP "stat\s+-c\s+['\"]?%Y['\"]?|stat\s+-f\s+['\"]?%m['\"]?" --type=shRepository: paudley/core_data Length of output: 104 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Show relevant section with line numbers (no nl dependency)
sed -n '110,170p' scripts/lib/data_cleanup.sh | cat -n
# Search for GNU-specific du/stat usage and any macOS fallbacks
rg -n --fixed-string "du -s -B" --type=sh .
rg -n --fixed-string "stat -f '%m'" --type=sh .
rg -nP "\bstat\s+-c\s+['\"]?%Y['\"]?" --type=sh .
rg -nP "stat\s+-c\s+['\"]?%Y['\"]?|stat\s+-f\s+['\"]?%m['\"]?" --type=sh .Repository: paudley/core_data Length of output: 2872 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Re-print the exact problematic block with line numbers
sed -n '120,150p' scripts/lib/data_cleanup.sh | cat -n
# Search repo for the same portability-sensitive commands
rg -n --fixed-strings "du -s -B1" --type=sh .
rg -nP "\bstat\s+-c\s+['\"]?%Y['\"]?" --type=sh .
rg -n --fixed-strings "stat -f '%m'" --type=sh .
rg -nP "stat\s+-c\s+['\"]?%Y['\"]?|stat\s+-f\s+['\"]?%m['\"]?" --type=sh .Repository: paudley/core_data Length of output: 1712 Fix macOS portability for
Proposed fix (for `stat`) local path
while IFS= read -r -d '' path; do
local modified
- modified=$(stat -c '%Y' "${path}")
+ if [[ "$(uname -s)" == "Darwin" ]]; then
+ modified=$(stat -f '%m' "${path}")
+ else
+ modified=$(stat -c '%Y' "${path}")
+ fi
if ((modified <= cutoff)); then🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ((modified <= cutoff)); then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local bytes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bytes=$(du -s -B1 "${path}" | awk '{print $1}') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+128
to
+133
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. 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
# Search for du usage and platform detection
rg -nP '\bdu\s+.*-[Bk]' --type=sh -C3Repository: paudley/core_data Length of output: 566 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
echo "== du usage with -B/-b flags =="
rg -nP '\bdu\s+.*-(B|b)\b' . || true
echo
echo "== du usage with bytes/size flags =="
rg -nP '\bdu\s+-(s|k|K|-h|b|B)\b' . || true
echo
echo "== platform detection for Darwin/BSD =="
rg -nP 'Darwin|uname\s+-s|OSTYPE|sysctl\s+-n\s+hw\.model' --glob='*.sh' --glob='*.bash' --glob='*.zsh' --glob='*.inc' --glob='*.plugin' . || trueRepository: paudley/core_data Length of output: 262 🌐 Web query:
💡 Result: The macOS Citations:
🌐 Web query:
💡 Result: On macOS, the -B blocksize option for the du command instructs it to calculate and display file system usage in units of the specified blocksize in bytes [1][2]. When used in conjunction with the -s (summary) option, the output will represent the total size of the specified file or directory expressed in those custom-sized blocks [1][2]. Important details regarding this behavior include: 1. Functionality: The -B blocksize option performs a calculation that estimates how much space the examined file hierarchy would occupy if it were stored on a file system using that specific block size [1][2]. 2. Precedence and Interaction: This option is distinct from other output flags like -h (human-readable), -k (kilobytes), -m (megabytes), or -g (gigabytes), as well as the BLOCKSIZE environment variable [1][2]. 3. Defaults: If neither -B nor other specific size options are provided, and the BLOCKSIZE environment variable is not set, macOS du defaults to displaying output in 512-byte blocks [3][4][2]. Note that macOS uses the BSD version of du, which differs from the GNU coreutils version found on many Linux distributions [4][5][6]. While both share the -B option, their internal implementations and the way they handle block-size calculations can vary [1][2][5]. Citations:
Adjust
🔧 Proposed fix (portable `du` size)- bytes=$(du -s -B1 "${path}" | awk '{print $1}')
+ bytes=$(du -sk "${path}" | awk '{print $1 * 1024}')📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidates+=("${path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidate_count=$((candidate_count + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| total_bytes=$((total_bytes + bytes)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+129
to
+137
Contributor
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. The use of GNU-specific flags for
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done < <(find "${backup_root}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${execute}" == "true" && "${force}" != "true" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local compose_state=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data_cleanup_compose_running || compose_state=$? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "${compose_state}" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[data-cleanup] refusing to delete while compose containers are running; rerun after shutdown or pass --force." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[data-cleanup] unable to determine compose state; pass --force to execute anyway." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${json}" == "true" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '{"mode":"%s","backup_root":"%s","older_than":"%s","candidates":%d,"bytes":%d,"paths":[' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "$([[ "${execute}" == "true" ]] && echo execute || echo dry-run)" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "$(data_cleanup_json_escape "${backup_root}")" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "$(data_cleanup_json_escape "${older_than}")" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "${candidate_count}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "${total_bytes}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local first=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in "${candidates[@]}"; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${first}" == "true" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| first=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf ',' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '"%s"' "$(data_cleanup_json_escape "${candidate}")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf ']}\n' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] mode: %s\n' "$([[ "${execute}" == "true" ]] && echo execute || echo dry-run)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] backup root: %s\n' "${backup_root}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] retention: older than %s\n' "${older_than}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] candidates: %d\n' "${candidate_count}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] reclaimable bytes: %d\n' "${total_bytes}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in "${candidates[@]}"; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s\n' "${candidate}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${execute}" != "true" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '[data-cleanup] dry run only; pass --execute to delete matching entries.\n' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "${execute}" == "true" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in "${candidates[@]}"; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -rf -- "${candidate}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+191
to
+193
Contributor
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. If
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Tests Module | ||
|
|
||
| The `tests` package verifies Core Data management behavior from the operator | ||
| boundary. Lightweight tests avoid Docker where possible, while integration tests | ||
| exercise Compose-managed services and persistent data workflows. | ||
|
|
||
| Tests that create temporary service data must isolate their data roots and must | ||
| not remove live directories outside their fixture-owned paths. Cleanup tests use | ||
| fake Compose binaries to validate command safety gates without depending on a | ||
| running container stack. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # SPDX-FileCopyrightText: 2026 Blackcat Informatics® Inc. | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| """Repository test suite contracts. | ||
|
|
||
| This package contains lightweight and integration tests for the Core Data | ||
| management tooling. The tests exercise operator-facing shell commands and | ||
| container workflows from outside the production code paths. | ||
|
|
||
| See Also: | ||
| MODULE.md: Test-suite contract and cleanup safety expectations. | ||
|
|
||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add escapes for tabs, carriage returns, and control characters.
The current implementation only escapes backslashes, quotes, and newlines. While this works for typical file paths, it may produce invalid JSON if paths contain tabs, carriage returns, or other control characters.
🔧 Proposed fix to handle additional escape sequences
data_cleanup_json_escape() { local value=$1 value=${value//\\/\\\\} value=${value//\"/\\\"} + value=${value//$'\t'/\\t} + value=${value//$'\r'/\\r} + value=${value//$'\f'/\\f} value=${value//$'\n'/\\n} printf '%s' "${value}" }📝 Committable suggestion
🤖 Prompt for AI Agents