Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 89 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Every module remains standalone: download only the check you need, verify its ch
## Safety contract

- Active scripts are read-only checks. They do not install packages, restart services, delete mail, modify firewall rules, or rewrite configuration.
- Scripts do not intentionally display credentials, message contents, machine identifiers, or unredacted public network addresses.
- Default script output does not intentionally display credentials, message contents, machine identifiers, or unredacted public network addresses. Explicit `--show-mounts`, `--show-units`, `--show-names`, and `--show-paths` options reveal only the selected local metadata.
- The master uses the checked-in `catalog.tsv` allowlist. It never sources modules, evaluates argument strings, downloads code, installs packages, or performs remediation.
- Catalog paths are constrained to regular executable files under a real, non-linked `scripts/` directory; linked catalogs, module directories, and module files are rejected.
- Remote commands download a version-tagged file, verify SHA-256, and execute only after verification succeeds.
Expand All @@ -16,7 +16,7 @@ Every module remains standalone: download only the check you need, verify its ch

## Master `onescript` CLI

The master requires a complete verified checkout containing `onescript`, `catalog.tsv`, `SHA256SUMS`, and `scripts/`. Wave 0 is not part of the existing `v2.0.0` release; a version-pinned installation command will be added only when its release is approved and published.
The master requires a complete verified checkout containing `onescript`, `catalog.tsv`, `SHA256SUMS`, and `scripts/`. The launcher and Linux server diagnostics wave are v2.1.0 release candidates and are not part of the existing `v2.0.0` release; version-pinned installation commands will be added only when that release is approved and published.

Verify the installed catalog:

Expand Down Expand Up @@ -98,6 +98,92 @@ One-line invocation:
(tmp_dir="$(mktemp -d)" && chmod 700 "$tmp_dir" && trap 'rm -rf "$tmp_dir"' EXIT && curl -fsSLo "$tmp_dir/exim-queue-health.sh" 'https://raw.githubusercontent.com/RomeoRaven/ONEScripts/v2.0.0/scripts/exim-queue-health.sh' && printf '%s %s\n' 'cf4606e3cd496dd9d8ca9db1281488507e3abeb2f45f460f1af070297a3fec22' "$tmp_dir/exim-queue-health.sh" | sha256sum -c - && chmod 700 "$tmp_dir/exim-queue-health.sh" && "$tmp_dir/exim-queue-health.sh")
```

## Linux server diagnostics wave (v2.1.0 candidate)

These modules are available from the current checkout. Findings use concise `key=value` output. A successful collection returns `0` even when `status=warning|critical|stale`; usage errors return `2`, and collection failures return `1`.

### `filesystem-health.sh`

Reports mounted-filesystem capacity and inode pressure by index, without device or mount names by default. Default warning and critical thresholds are 85% and 95%; use `--show-mounts` for local diagnosis.

```bash
./scripts/filesystem-health.sh
```

### `memory-pressure.sh`

Reports memory and swap use, one-minute load normalized by online CPU count, and Linux PSI values when available. It does not list processes or command lines.

```bash
./scripts/memory-pressure.sh
```

### `service-health.sh`

Reports aggregate systemd state and failed-unit count. Unit names remain omitted unless `--show-units` is explicitly supplied.

```bash
./scripts/service-health.sh
```

### `listener-exposure.sh`

Lists listener protocol, port, and bind scope (`loopback`, `specific-address`, or `all-interfaces`) without displaying literal addresses or process details.

```bash
./scripts/listener-exposure.sh
```

### `update-posture.sh`

Counts updates visible in existing APT or DNF metadata and checks the reboot-required marker. It never refreshes metadata, lists package names, installs updates, or reboots.

```bash
./scripts/update-posture.sh
```

### `backup-freshness.sh`

Finds the newest regular file among explicit files and immediate directory entries. It does not recurse and reports age/count only; use `--show-names` or `--show-paths` explicitly for local diagnosis.

```bash
./scripts/backup-freshness.sh --max-hours 24 /srv/backups
```

### `endpoint-health.sh`

Probes explicit HTTP(S) endpoints and reports status and latency by input index. URLs, query strings, headers, and response bodies are omitted.

```bash
./scripts/endpoint-health.sh https://example.com/health
```

### `tls-expiry.sh`

Verifies explicit TLS targets and reports certificate lifetime by input index. Hostnames, addresses, subjects, and issuers are omitted.

```bash
./scripts/tls-expiry.sh example.com:443
```

### `container-health.sh`

Reports aggregate Docker or Podman running, stopped, and unhealthy counts. Names, images, IDs, ports, labels, and commands are omitted. If Docker is installed but unavailable, a usable Podman runtime is tried.

```bash
./scripts/container-health.sh
```

### `log-storage.sh`

Measures allocated size for explicit log directories, defaulting to `/var/log`, without reading log contents. Paths are omitted unless `--show-paths` is supplied. If permissions hide a child directory but `du` still returns a numeric total, the result is retained and marked `collection_complete=no`.

```bash
./scripts/log-storage.sh
```

These modules intentionally have no remote one-line invocation until the v2.1.0 tag and release checksums are approved and published.

## Direct module use

Clone the repository and run a script directly:
Expand Down Expand Up @@ -134,7 +220,7 @@ A new active module must:
4. Include behavioral coverage where practical.
5. Add one valid `catalog.tsv` row with a stable ID, category, display name, description, requirements summary, path, and `yes|no` safe-default flag.
6. Pass Bash parsing, ShellCheck, the repository test suite, launcher tests, and checksum verification.
7. Add the module checksum to `SHA256SUMS` and include one version-pinned, checksum-bound invocation in this README.
7. Add the module checksum to `SHA256SUMS`, document direct checkout use, and add a version-pinned checksum-bound remote invocation when the containing release is published.

Ordinary module additions do not require launcher code changes. Use `menu_safe=no` when a module cannot run meaningfully or safely without explicit arguments; it remains available through direct execution and `onescript run`.

Expand Down
14 changes: 12 additions & 2 deletions SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
877de32b38eaa36295cf16276e1073ddc306f9312663ee5291e5d44aed641c58 onescript
705c2d2342e4a6180a917d3de20e734fb9d312d827aba98cf5eb94507e9fdb0e catalog.tsv
2af806038a55f729f6cff932ea9ac825a9cb0318901176d023c5979d5a685325 catalog.tsv
d260a50a2fb3fa0af09edf310da8006246924cf4319a05ad2935c19fd91911ed scripts/vps-profile.sh
5bb1e14491437c42cf6d5169186a2545176dcbbf5ba4367d84aabf0e851f0a23 scripts/cpanel-followup-triage.sh
156ea405829ec5c27ed7a462a23e2fe9752704467c93fbc652aef205d24ca65d scripts/directory-sizes.sh
cf4606e3cd496dd9d8ca9db1281488507e3abeb2f45f460f1af070297a3fec22 scripts/exim-queue-health.sh
d260a50a2fb3fa0af09edf310da8006246924cf4319a05ad2935c19fd91911ed scripts/vps-profile.sh
0b4608698315cf1ed19b532e453ff08fe9aeab93a0a8492793217c9002c5a12f scripts/filesystem-health.sh
859fcefc6b631f9b63e28db8c2f58372e55adfe567e70c3d2317e609b5543d59 scripts/memory-pressure.sh
c35c04f1f5f54eb945b46cbd6ebecb05146f02baf8f7de5bbe967c494ec5bb56 scripts/service-health.sh
a12029e59604066a57edf0bd12668a34031779d59838957d7991973580ea8c2f scripts/listener-exposure.sh
7e04e99afcaad2d704e69f5efc5e4b1866804f451cf90e730f83419b42ff1da9 scripts/update-posture.sh
fe3e47781e5694ebcc2c260df97bb3421fa5e13fa353c0e25e00c9ecffaf2a8b scripts/backup-freshness.sh
46d3961f9364b9ccf7cda154dcef52810855aaf6040ac08a416d641b014e84fb scripts/endpoint-health.sh
ea624496235f3d78cd38be3173f6bb18d898d7cc25b71fe36d678459691b61f0 scripts/tls-expiry.sh
6166f768a4f242db8e077438ebdc2736ffbafd5306dd0d1adbb1da7b0b75b26b scripts/container-health.sh
3eb2acfbbaf9643ebf50b7822fa6e6d7908f421abb7edfc2521ac5ebafa0422a scripts/log-storage.sh
10 changes: 10 additions & 0 deletions catalog.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ vps-profile system VPS profile Privacy-reduced VPS and cPanel profile Bash 4+, s
cpanel-followup-triage cpanel cPanel follow-up triage Focused cPanel service, mail queue, and listener triage Bash 4+, systemctl, ss; Exim utilities when available scripts/cpanel-followup-triage.sh yes
directory-sizes storage Directory sizes Immediate subdirectory disk usage, largest first Bash 4+, find, du, sort scripts/directory-sizes.sh yes
exim-queue-health mail Exim queue health Privacy-safe Exim queue counts Bash 4+, Exim; exiqgrep recommended scripts/exim-queue-health.sh yes
filesystem-health system Filesystem health Capacity and inode pressure without device names Bash 4+, GNU df scripts/filesystem-health.sh yes
memory-pressure system Memory pressure Memory, swap, load, and PSI summary Bash 4+, Linux procfs, awk scripts/memory-pressure.sh yes
service-health services Service health Aggregate systemd state and failed-unit count Bash 4+, systemctl scripts/service-health.sh yes
listener-exposure network Listener exposure Address-free listener port and scope inventory Bash 4+, ss scripts/listener-exposure.sh yes
update-posture packages Update posture Cached APT or DNF update and reboot posture Bash 4+, apt-get or dnf scripts/update-posture.sh yes
backup-freshness backup Backup freshness Newest explicit backup artifact age Bash 4+, find, stat, GNU date, mktemp scripts/backup-freshness.sh no
endpoint-health network Endpoint health HTTP status and latency without URL disclosure Bash 4+, curl scripts/endpoint-health.sh no
tls-expiry network TLS expiry TLS verification and expiry without target disclosure Bash 4+, OpenSSL, GNU date, timeout scripts/tls-expiry.sh no
container-health containers Container health Aggregate Docker or Podman container state Bash 4+; optional Docker or Podman scripts/container-health.sh yes
log-storage storage Log storage Allocated log-directory size without content reads Bash 4+, GNU du scripts/log-storage.sh yes
150 changes: 150 additions & 0 deletions scripts/backup-freshness.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# Report freshness of explicitly selected backup files or directories.
set -euo pipefail
export LC_ALL=C

VERSION="2.1.0"
MAX_HOURS=24
SHOW_PATHS=0
SHOW_NAMES=0
TARGETS=()

usage() {
printf '%s\n' \
'Usage: backup-freshness.sh [--max-hours N] [--show-names] [--show-paths] FILE_OR_DIRECTORY...' \
'' \
'Find the newest regular file among explicit files and immediate directory entries.' \
'Directories are not searched recursively. Artifact names and paths are omitted by default.' \
'' \
'Options:' \
' --max-hours N Maximum acceptable age (default: 24)' \
' --show-names Include the latest basename in output' \
' --show-paths Include the latest full path and basename in output' \
' -h, --help Show this help' \
' --version Print the script version'
}

normalize_uint() {
local raw="$1" max="$2" normalized
[[ "$raw" =~ ^0*([0-9]+)$ ]] || return 1
normalized="${BASH_REMATCH[1]}"
if ((${#normalized} > ${#max})) \
|| { ((${#normalized} == ${#max})) && [[ "$normalized" > "$max" ]]; }; then
return 1
fi
printf '%s\n' "$normalized"
}

while (($#)); do
case "$1" in
--max-hours)
(($# >= 2)) || { printf 'ERROR: --max-hours requires a value\n' >&2; exit 2; }
MAX_HOURS="$2"
shift
;;
--show-names) SHOW_NAMES=1 ;;
--show-paths) SHOW_PATHS=1; SHOW_NAMES=1 ;;
-h|--help) usage; exit 0 ;;
--version) printf '%s\n' "$VERSION"; exit 0 ;;
--)
shift
while (($#)); do TARGETS+=("$1"); shift; done
break
;;
-*) printf 'ERROR: unsupported option: %s\n' "$1" >&2; usage >&2; exit 2 ;;
*) TARGETS+=("$1") ;;
esac
shift
done

if ! MAX_HOURS="$(normalize_uint "$MAX_HOURS" 2147483647)" || ((MAX_HOURS < 1)); then
printf 'ERROR: --max-hours must be a positive integer\n' >&2
exit 2
fi
if ((${#TARGETS[@]} == 0)); then
printf 'ERROR: at least one backup file or directory is required\n' >&2
exit 2
fi

FIND_BIN="${FIND_BIN:-$(command -v find 2>/dev/null || true)}"
if [[ -z "$FIND_BIN" || ! -x "$FIND_BIN" ]]; then
printf 'ERROR: find was not found\n' >&2
exit 1
fi
DISCOVERY_FILE=''
cleanup() {
[[ -z "$DISCOVERY_FILE" ]] || rm -f -- "$DISCOVERY_FILE"
}
trap cleanup EXIT

candidates=()
for target in "${TARGETS[@]}"; do
[[ "$target" == -* ]] && target="./$target"
if [[ -L "$target" ]]; then
printf 'ERROR: linked backup targets are not accepted\n' >&2
exit 1
elif [[ -f "$target" ]]; then
candidates+=("$target")
elif [[ -d "$target" ]]; then
if [[ -z "$DISCOVERY_FILE" ]]; then
old_umask="$(umask)"
umask 077
if ! DISCOVERY_FILE="$(mktemp)"; then
umask "$old_umask"
printf 'ERROR: backup discovery workspace could not be created\n' >&2
exit 1
fi
umask "$old_umask"
fi
if ! "$FIND_BIN" -P "$target" -mindepth 1 -maxdepth 1 -type f -print0 >"$DISCOVERY_FILE" 2>/dev/null; then
printf 'ERROR: backup directory could not be enumerated\n' >&2
exit 1
fi
while IFS= read -r -d '' file; do
candidates+=("$file")
done <"$DISCOVERY_FILE"
else
printf 'ERROR: backup target is not a regular file or directory\n' >&2
exit 1
fi
done

candidate_count="${#candidates[@]}"
if ((candidate_count == 0)); then
printf 'status=missing\ncandidate_count=0\nlatest_name=none\nage_hours=-1\nnames_omitted=yes\npaths_omitted=yes\n'
exit 0
fi

latest=''
latest_mtime=-1
for file in "${candidates[@]}"; do
if ! mtime="$(stat -c %Y -- "$file" 2>/dev/null)" || [[ ! "$mtime" =~ ^[0-9]+$ ]]; then
printf 'ERROR: backup candidate metadata could not be read\n' >&2
exit 1
fi
if ((mtime > latest_mtime)); then
latest_mtime="$mtime"
latest="$file"
fi
done

now="$(date +%s)"
age_seconds=$((now - latest_mtime))
((age_seconds >= 0)) || age_seconds=0
age_hours=$((age_seconds / 3600))
status='ok'
((age_seconds <= MAX_HOURS * 3600)) || status='stale'

latest_name='omitted'
names_omitted='yes'
if ((SHOW_NAMES)); then
latest_name="$(basename -- "$latest")"
names_omitted='no'
fi
printf 'status=%s\ncandidate_count=%d\nlatest_name=%s\nage_hours=%d\nnames_omitted=%s\n' \
"$status" "$candidate_count" "$latest_name" "$age_hours" "$names_omitted"
if ((SHOW_PATHS)); then
printf 'paths_omitted=no\nlatest_path=%s\n' "$latest"
else
printf 'paths_omitted=yes\n'
fi
82 changes: 82 additions & 0 deletions scripts/container-health.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Report aggregate Docker or Podman container state without names or image metadata.
set -euo pipefail
export LC_ALL=C

VERSION="2.1.0"

usage() {
printf '%s\n' \
'Usage: container-health.sh' \
'' \
'Report aggregate Docker or Podman container state.' \
'Container names, images, ports, labels, IDs, and commands are omitted.' \
'' \
'Options:' \
' -h, --help Show this help' \
' --version Print the script version'
}

case "${1:-}" in
-h|--help) usage; exit 0 ;;
--version) printf '%s\n' "$VERSION"; exit 0 ;;
'') ;;
*) printf 'ERROR: this script accepts no arguments\n' >&2; usage >&2; exit 2 ;;
esac
if (($# > 1)); then
printf 'ERROR: this script accepts no positional arguments\n' >&2
exit 2
fi

DOCKER_BIN="${DOCKER_BIN:-$(command -v docker 2>/dev/null || true)}"
PODMAN_BIN="${PODMAN_BIN:-$(command -v podman 2>/dev/null || true)}"
engine='none'
engine_bin=''
runtime_cli_present=0
if [[ -n "$DOCKER_BIN" && -x "$DOCKER_BIN" ]]; then
runtime_cli_present=1
if "$DOCKER_BIN" info --format '{{.ServerVersion}}' >/dev/null 2>&1; then
engine='docker'
engine_bin="$DOCKER_BIN"
fi
fi
if [[ -z "$engine_bin" && -n "$PODMAN_BIN" && -x "$PODMAN_BIN" ]]; then
runtime_cli_present=1
if "$PODMAN_BIN" info --format '{{.Version.Version}}' >/dev/null 2>&1; then
engine='podman'
engine_bin="$PODMAN_BIN"
fi
fi
if [[ -z "$engine_bin" ]]; then
if ((runtime_cli_present)); then
printf 'ERROR: installed container runtimes are unavailable or inaccessible\n' >&2
exit 1
fi
printf 'status=unavailable\nengine=none\ncontainer_count=0\nrunning_count=0\nstopped_count=0\nunhealthy_count=0\ncontainer_names_omitted=yes\n'
exit 0
fi
if ! status_rows="$("$engine_bin" ps -a --format '{{.Status}}' 2>/dev/null)"; then
printf 'ERROR: container state collection failed\n' >&2
exit 1
fi

container_count=0
running_count=0
stopped_count=0
unhealthy_count=0
while IFS= read -r row; do
[[ -n "$row" ]] || continue
container_count=$((container_count + 1))
case "$row" in
Up*|up*|Running*|running*) running_count=$((running_count + 1)) ;;
*) stopped_count=$((stopped_count + 1)) ;;
esac
if [[ "${row,,}" == *unhealthy* ]]; then
unhealthy_count=$((unhealthy_count + 1))
fi
done <<<"$status_rows"

status='ok'
((unhealthy_count == 0)) || status='warning'
printf 'status=%s\nengine=%s\ncontainer_count=%d\nrunning_count=%d\nstopped_count=%d\nunhealthy_count=%d\ncontainer_names_omitted=yes\n' \
"$status" "$engine" "$container_count" "$running_count" "$stopped_count" "$unhealthy_count"
Loading
Loading