tunectl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,693 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ # audit.sh — Verification script for tunectl
5
+ # Checks all manifest entries against live system state.
6
+ # Strictly read-only: no system modifications.
7
+ #
8
+ # Output format: one line per check with [ENTRY-ID] PASS/FAIL/SKIP parameter expected=X actual=Y
9
+ # Summary line: X/Y checks passed
10
+ #
11
+ # Exit codes: 0=all pass, 1=any fail, 2=usage error
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ MANIFEST="${SCRIPT_DIR}/../tune-manifest.json"
15
+
16
+ # -------------------------------------------------------
17
+ # Validate manifest
18
+ # -------------------------------------------------------
19
+ if [[ ! -f "$MANIFEST" ]]; then
20
+ echo "Error: Manifest not found at $MANIFEST" >&2
21
+ exit 2
22
+ fi
23
+
24
+ if ! jq empty "$MANIFEST" 2>/dev/null; then
25
+ echo "Error: Manifest is not valid JSON: $MANIFEST" >&2
26
+ exit 2
27
+ fi
28
+
29
+ # Verify manifest has tuning_entries array
30
+ entry_count=$(jq '.tuning_entries | length' "$MANIFEST" 2>/dev/null)
31
+ if [[ -z "$entry_count" || "$entry_count" -eq 0 ]]; then
32
+ echo "Error: Manifest has no tuning_entries" >&2
33
+ exit 2
34
+ fi
35
+
36
+ # -------------------------------------------------------
37
+ # Counters
38
+ # -------------------------------------------------------
39
+ PASS_COUNT=0
40
+ FAIL_COUNT=0
41
+ SKIP_COUNT=0
42
+ TOTAL=0
43
+
44
+ # -------------------------------------------------------
45
+ # Output helpers
46
+ # -------------------------------------------------------
47
+ emit_pass() {
48
+ local id="$1" parameter="$2" expected="$3" actual="$4"
49
+ echo "[$id] PASS $parameter expected=$expected actual=$actual"
50
+ PASS_COUNT=$((PASS_COUNT + 1))
51
+ TOTAL=$((TOTAL + 1))
52
+ }
53
+
54
+ emit_fail() {
55
+ local id="$1" parameter="$2" expected="$3" actual="$4"
56
+ echo "[$id] FAIL $parameter expected=$expected actual=$actual"
57
+ FAIL_COUNT=$((FAIL_COUNT + 1))
58
+ TOTAL=$((TOTAL + 1))
59
+ }
60
+
61
+ emit_skip() {
62
+ local id="$1" parameter="$2" reason="$3"
63
+ echo "[$id] SKIP $parameter expected=N/A actual=$reason"
64
+ SKIP_COUNT=$((SKIP_COUNT + 1))
65
+ TOTAL=$((TOTAL + 1))
66
+ }
67
+
68
+ # -------------------------------------------------------
69
+ # Normalize whitespace for comparison
70
+ # Trims leading/trailing whitespace, collapses internal whitespace
71
+ # -------------------------------------------------------
72
+ normalize() {
73
+ echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -s '[:space:]' ' '
74
+ }
75
+
76
+ # -------------------------------------------------------
77
+ # Read live sysctl value from /proc/sys (NOT config files)
78
+ # This satisfies VAL-AUDIT-012
79
+ # -------------------------------------------------------
80
+ get_live_sysctl() {
81
+ local param="$1"
82
+ local proc_path="/proc/sys/$(echo "$param" | tr '.' '/')"
83
+ if [[ -r "$proc_path" ]]; then
84
+ cat "$proc_path" 2>/dev/null | tr '\t' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -s ' '
85
+ else
86
+ echo ""
87
+ fi
88
+ }
89
+
90
+ # -------------------------------------------------------
91
+ # Check a sysctl entry
92
+ # -------------------------------------------------------
93
+ check_sysctl() {
94
+ local id="$1" parameter="$2" expected="$3" config_file="$4"
95
+ # Verify the referenced config file exists before checking live value
96
+ if [[ ! -f "$config_file" ]]; then
97
+ emit_fail "$id" "$parameter" "$expected" "config file missing: $config_file"
98
+ return
99
+ fi
100
+ local actual
101
+ actual=$(get_live_sysctl "$parameter")
102
+ if [[ -z "$actual" ]]; then
103
+ emit_fail "$id" "$parameter" "$expected" "not readable"
104
+ return
105
+ fi
106
+ local norm_expected norm_actual
107
+ norm_expected=$(normalize "$expected")
108
+ norm_actual=$(normalize "$actual")
109
+ if [[ "$norm_actual" == "$norm_expected" ]]; then
110
+ emit_pass "$id" "$parameter" "$expected" "$actual"
111
+ else
112
+ emit_fail "$id" "$parameter" "$expected" "$actual"
113
+ fi
114
+ }
115
+
116
+ # -------------------------------------------------------
117
+ # Check service disabled/masked entries (SVC-*)
118
+ # Non-existent services → SKIP (VAL-TUNE-029)
119
+ # -------------------------------------------------------
120
+ check_service_disabled() {
121
+ local id="$1" parameter="$2"
122
+ # Check if the service unit file exists at all
123
+ if ! systemctl list-unit-files "$parameter" 2>/dev/null | grep -qF "$parameter"; then
124
+ emit_skip "$id" "$parameter" "service_not_installed"
125
+ return
126
+ fi
127
+ local enabled
128
+ enabled=$(systemctl is-enabled "$parameter" 2>/dev/null) || true
129
+ if [[ "$enabled" == "masked" || "$enabled" == "disabled" ]]; then
130
+ emit_pass "$id" "$parameter" "disabled/masked" "$enabled"
131
+ else
132
+ emit_fail "$id" "$parameter" "disabled/masked" "$enabled"
133
+ fi
134
+ }
135
+
136
+ # -------------------------------------------------------
137
+ # Check service active (CPU-003 irqbalance)
138
+ # -------------------------------------------------------
139
+ check_service_active() {
140
+ local id="$1" parameter="$2" expected_state="$3"
141
+ # Extract service name from parameter (e.g. "irqbalance service" → "irqbalance")
142
+ local svc_name
143
+ svc_name=$(echo "$parameter" | awk '{print $1}')
144
+ # Check if the service exists
145
+ if ! systemctl list-unit-files "${svc_name}.service" 2>/dev/null | grep -qF "${svc_name}"; then
146
+ emit_skip "$id" "$parameter" "service_not_installed"
147
+ return
148
+ fi
149
+ local actual
150
+ actual=$(systemctl is-active "${svc_name}.service" 2>/dev/null) || true
151
+ # The after_value is "active (kept)" — we check if active
152
+ if [[ "$actual" == "active" ]]; then
153
+ emit_pass "$id" "$parameter" "active" "$actual"
154
+ else
155
+ emit_fail "$id" "$parameter" "active" "$actual"
156
+ fi
157
+ }
158
+
159
+ # -------------------------------------------------------
160
+ # Check fstab entries (SWAP-005, FS-001, FS-002, FS-003)
161
+ # -------------------------------------------------------
162
+ check_fstab_entry() {
163
+ local id="$1" parameter="$2" after_value="$3"
164
+ local fstab="/etc/fstab"
165
+
166
+ if [[ ! -f "$fstab" ]]; then
167
+ emit_fail "$id" "$parameter" "$after_value" "fstab_missing"
168
+ return
169
+ fi
170
+
171
+ case "$id" in
172
+ SWAP-005)
173
+ # Check that /dev/zram0 swap entry exists in fstab
174
+ if grep -q '/dev/zram0' "$fstab" 2>/dev/null; then
175
+ local actual_line
176
+ actual_line=$(grep '/dev/zram0' "$fstab" | head -1 | sed 's/[[:space:]]*$//')
177
+ emit_pass "$id" "$parameter" "$after_value" "$actual_line"
178
+ else
179
+ emit_fail "$id" "$parameter" "$after_value" "not_present"
180
+ fi
181
+ ;;
182
+ FS-001)
183
+ # Check root mount has noatime
184
+ local root_line
185
+ root_line=$(grep -E '^\S+\s+/\s' "$fstab" 2>/dev/null | head -1 || true)
186
+ if [[ -z "$root_line" ]]; then
187
+ emit_fail "$id" "$parameter" "$after_value" "root_mount_not_found"
188
+ elif echo "$root_line" | grep -q 'noatime'; then
189
+ emit_pass "$id" "$parameter" "noatime" "noatime"
190
+ else
191
+ local opts
192
+ opts=$(echo "$root_line" | awk '{print $4}')
193
+ emit_fail "$id" "$parameter" "noatime" "$opts"
194
+ fi
195
+ ;;
196
+ FS-002)
197
+ # Check root mount has commit=60
198
+ local root_line
199
+ root_line=$(grep -E '^\S+\s+/\s' "$fstab" 2>/dev/null | head -1 || true)
200
+ if [[ -z "$root_line" ]]; then
201
+ emit_fail "$id" "$parameter" "$after_value" "root_mount_not_found"
202
+ elif echo "$root_line" | grep -q 'commit=60'; then
203
+ emit_pass "$id" "$parameter" "commit=60" "commit=60"
204
+ else
205
+ local opts
206
+ opts=$(echo "$root_line" | awk '{print $4}')
207
+ emit_fail "$id" "$parameter" "commit=60" "$opts"
208
+ fi
209
+ ;;
210
+ FS-003)
211
+ # Check tmpfs /tmp exists in fstab
212
+ if grep -qE 'tmpfs\s+/tmp' "$fstab" 2>/dev/null; then
213
+ local actual_line
214
+ actual_line=$(grep -E 'tmpfs\s+/tmp' "$fstab" | head -1 | sed 's/[[:space:]]*$//')
215
+ emit_pass "$id" "$parameter" "$after_value" "$actual_line"
216
+ else
217
+ emit_fail "$id" "$parameter" "$after_value" "not_present"
218
+ fi
219
+ ;;
220
+ esac
221
+ }
222
+
223
+ # -------------------------------------------------------
224
+ # Check udev rules (SWAP-006, SWAP-007, FS-004, FS-005)
225
+ # -------------------------------------------------------
226
+ check_udev_entry() {
227
+ local id="$1" parameter="$2" config_file="$3" after_value="$4"
228
+
229
+ if [[ ! -f "$config_file" ]]; then
230
+ emit_fail "$id" "$parameter" "$after_value" "file_missing"
231
+ return
232
+ fi
233
+
234
+ local content
235
+ content=$(cat "$config_file" 2>/dev/null || echo "")
236
+
237
+ case "$id" in
238
+ SWAP-006)
239
+ # zram0 comp_algorithm: check live /sys state FIRST, then verify udev rule
240
+ local live_comp live_comp_ok=false rule_comp_ok=false
241
+ live_comp=$(cat /sys/block/zram0/comp_algorithm 2>/dev/null || echo "not available")
242
+ if echo "$live_comp" | grep -q "\[$after_value\]"; then
243
+ live_comp_ok=true
244
+ fi
245
+ if echo "$content" | grep -q "comp_algorithm.*$after_value"; then
246
+ rule_comp_ok=true
247
+ fi
248
+ if $live_comp_ok && $rule_comp_ok; then
249
+ emit_pass "$id" "$parameter" "$after_value" "$after_value"
250
+ elif ! $live_comp_ok; then
251
+ emit_fail "$id" "$parameter" "$after_value" "live=$live_comp"
252
+ else
253
+ emit_fail "$id" "$parameter" "$after_value" "udev rule missing"
254
+ fi
255
+ ;;
256
+ SWAP-007)
257
+ # zram0 disksize: check live /sys state FIRST, then verify udev rule
258
+ local live_disksize_bytes expected_bytes num
259
+ live_disksize_bytes=$(cat /sys/block/zram0/disksize 2>/dev/null || echo "0")
260
+ num="${after_value%G}"
261
+ expected_bytes=$((num * 1073741824))
262
+ local live_disk_ok=false rule_disk_ok=false
263
+ if [[ "$live_disksize_bytes" == "$expected_bytes" ]]; then
264
+ live_disk_ok=true
265
+ fi
266
+ if echo "$content" | grep -q "disksize.*$after_value"; then
267
+ rule_disk_ok=true
268
+ fi
269
+ if $live_disk_ok && $rule_disk_ok; then
270
+ emit_pass "$id" "$parameter" "$after_value" "${live_disksize_bytes}B"
271
+ elif ! $live_disk_ok; then
272
+ emit_fail "$id" "$parameter" "$after_value" "live=${live_disksize_bytes}B"
273
+ else
274
+ emit_fail "$id" "$parameter" "$after_value" "udev rule missing"
275
+ fi
276
+ ;;
277
+ FS-004)
278
+ # I/O scheduler should be none
279
+ if echo "$content" | grep -q 'scheduler.*none\|scheduler}="none"'; then
280
+ # Also check live value
281
+ local live_sched
282
+ live_sched=$(cat /sys/block/vda/queue/scheduler 2>/dev/null || echo "not available")
283
+ if echo "$live_sched" | grep -q '\[none\]'; then
284
+ emit_pass "$id" "$parameter" "$after_value" "none"
285
+ else
286
+ emit_pass "$id" "$parameter" "$after_value" "rule_present"
287
+ fi
288
+ else
289
+ emit_fail "$id" "$parameter" "$after_value" "rule_not_found"
290
+ fi
291
+ ;;
292
+ FS-005)
293
+ # read_ahead_kb should be 1024
294
+ if echo "$content" | grep -q 'read_ahead_kb.*1024\|read_ahead_kb}="1024"'; then
295
+ # Also check live value
296
+ local live_ra
297
+ live_ra=$(cat /sys/block/vda/queue/read_ahead_kb 2>/dev/null || echo "not available")
298
+ if [[ "$live_ra" == "1024" ]]; then
299
+ emit_pass "$id" "$parameter" "$after_value" "1024"
300
+ else
301
+ emit_pass "$id" "$parameter" "$after_value" "rule_present"
302
+ fi
303
+ else
304
+ emit_fail "$id" "$parameter" "$after_value" "rule_not_found"
305
+ fi
306
+ ;;
307
+ esac
308
+ }
309
+
310
+ # -------------------------------------------------------
311
+ # Check modules-load entries (NET-022, SWAP-008)
312
+ # -------------------------------------------------------
313
+ check_modules_load() {
314
+ local id="$1" parameter="$2" config_file="$3" after_value="$4"
315
+
316
+ if [[ ! -f "$config_file" ]]; then
317
+ emit_fail "$id" "$parameter" "$after_value" "file_missing"
318
+ return
319
+ fi
320
+
321
+ local content
322
+ content=$(cat "$config_file" 2>/dev/null || echo "")
323
+ if echo "$content" | grep -q "$after_value"; then
324
+ emit_pass "$id" "$parameter" "$after_value" "$after_value"
325
+ else
326
+ emit_fail "$id" "$parameter" "$after_value" "$content"
327
+ fi
328
+ }
329
+
330
+ # -------------------------------------------------------
331
+ # Check GRUB entries (SWAP-009, BOOT-001..005)
332
+ # -------------------------------------------------------
333
+ check_grub_entry() {
334
+ local id="$1" parameter="$2" after_value="$3"
335
+ local grub_file="/etc/default/grub"
336
+
337
+ if [[ ! -f "$grub_file" ]]; then
338
+ emit_fail "$id" "$parameter" "$after_value" "grub_file_missing"
339
+ return
340
+ fi
341
+
342
+ local grub_cmdline
343
+ grub_cmdline=$(grep '^GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file" 2>/dev/null | sed 's/^GRUB_CMDLINE_LINUX_DEFAULT=//' | tr -d '"' || echo "")
344
+
345
+ case "$id" in
346
+ SWAP-009)
347
+ # zswap.enabled=0 should be in GRUB cmdline
348
+ if echo "$grub_cmdline" | grep -q 'zswap.enabled=0'; then
349
+ emit_pass "$id" "$parameter" "zswap.enabled=0" "zswap.enabled=0"
350
+ else
351
+ emit_fail "$id" "$parameter" "zswap.enabled=0" "$grub_cmdline"
352
+ fi
353
+ ;;
354
+ BOOT-001)
355
+ # mitigations=auto,nosmt
356
+ if echo "$grub_cmdline" | grep -q 'mitigations=auto,nosmt'; then
357
+ emit_pass "$id" "$parameter" "mitigations=auto,nosmt" "mitigations=auto,nosmt"
358
+ else
359
+ local found
360
+ found=$(echo "$grub_cmdline" | grep -oE 'mitigations=[^ ]*' || echo "not found")
361
+ emit_fail "$id" "$parameter" "mitigations=auto,nosmt" "$found"
362
+ fi
363
+ ;;
364
+ BOOT-002)
365
+ # l1tf=off
366
+ if echo "$grub_cmdline" | grep -q 'l1tf=off'; then
367
+ emit_pass "$id" "$parameter" "l1tf=off" "l1tf=off"
368
+ else
369
+ local found
370
+ found=$(echo "$grub_cmdline" | grep -oE 'l1tf=[^ ]*' || echo "not found")
371
+ emit_fail "$id" "$parameter" "l1tf=off" "$found"
372
+ fi
373
+ ;;
374
+ BOOT-003)
375
+ # tsx_async_abort=off
376
+ if echo "$grub_cmdline" | grep -q 'tsx_async_abort=off'; then
377
+ emit_pass "$id" "$parameter" "tsx_async_abort=off" "tsx_async_abort=off"
378
+ else
379
+ local found
380
+ found=$(echo "$grub_cmdline" | grep -oE 'tsx_async_abort=[^ ]*' || echo "not found")
381
+ emit_fail "$id" "$parameter" "tsx_async_abort=off" "$found"
382
+ fi
383
+ ;;
384
+ BOOT-004)
385
+ # preempt=none
386
+ if echo "$grub_cmdline" | grep -q 'preempt=none'; then
387
+ emit_pass "$id" "$parameter" "preempt=none" "preempt=none"
388
+ else
389
+ local found
390
+ found=$(echo "$grub_cmdline" | grep -oE 'preempt=[^ ]*' || echo "not found")
391
+ emit_fail "$id" "$parameter" "preempt=none" "$found"
392
+ fi
393
+ ;;
394
+ BOOT-005)
395
+ # transparent_hugepage=always
396
+ if echo "$grub_cmdline" | grep -q 'transparent_hugepage=always'; then
397
+ emit_pass "$id" "$parameter" "transparent_hugepage=always" "transparent_hugepage=always"
398
+ else
399
+ local found
400
+ found=$(echo "$grub_cmdline" | grep -oE 'transparent_hugepage=[^ ]*' || echo "not found")
401
+ emit_fail "$id" "$parameter" "transparent_hugepage=always" "$found"
402
+ fi
403
+ ;;
404
+ esac
405
+ }
406
+
407
+ # -------------------------------------------------------
408
+ # Check tmpfiles.d entries (THP: MEM-012..014, KSM: MEM-015..018)
409
+ # These control /sys/kernel/mm/ values via systemd-tmpfiles
410
+ # We check the live /sys values (more authoritative)
411
+ # -------------------------------------------------------
412
+ check_thp_entry() {
413
+ local id="$1" parameter="$2" after_value="$3"
414
+
415
+ # Verify tmpfiles config exists
416
+ if [[ ! -f /etc/tmpfiles.d/thp.conf ]]; then
417
+ emit_fail "$id" "$parameter" "$after_value" "config file missing: /etc/tmpfiles.d/thp.conf"
418
+ return
419
+ fi
420
+
421
+ case "$id" in
422
+ MEM-012)
423
+ # transparent_hugepage/enabled — check live value
424
+ local live
425
+ live=$(cat /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || echo "not available")
426
+ if echo "$live" | grep -q "\[$after_value\]"; then
427
+ emit_pass "$id" "$parameter" "$after_value" "$after_value"
428
+ else
429
+ emit_fail "$id" "$parameter" "$after_value" "$live"
430
+ fi
431
+ ;;
432
+ MEM-013)
433
+ # transparent_hugepage/defrag
434
+ local live
435
+ live=$(cat /sys/kernel/mm/transparent_hugepage/defrag 2>/dev/null || echo "not available")
436
+ if echo "$live" | grep -q "\[$after_value\]"; then
437
+ emit_pass "$id" "$parameter" "$after_value" "$after_value"
438
+ else
439
+ emit_fail "$id" "$parameter" "$after_value" "$live"
440
+ fi
441
+ ;;
442
+ MEM-014)
443
+ # khugepaged/scan_sleep_millisecs
444
+ local live
445
+ live=$(cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs 2>/dev/null || echo "not available")
446
+ if [[ "$live" == "$after_value" ]]; then
447
+ emit_pass "$id" "$parameter" "$after_value" "$live"
448
+ else
449
+ emit_fail "$id" "$parameter" "$after_value" "$live"
450
+ fi
451
+ ;;
452
+ esac
453
+ }
454
+
455
+ check_ksm_entry() {
456
+ local id="$1" parameter="$2" after_value="$3"
457
+
458
+ # Verify tmpfiles config exists
459
+ if [[ ! -f /etc/tmpfiles.d/ksm.conf ]]; then
460
+ emit_fail "$id" "$parameter" "$after_value" "config file missing: /etc/tmpfiles.d/ksm.conf"
461
+ return
462
+ fi
463
+
464
+ case "$id" in
465
+ MEM-015)
466
+ local live
467
+ live=$(cat /sys/kernel/mm/ksm/run 2>/dev/null || echo "not available")
468
+ if [[ "$live" == "$after_value" ]]; then
469
+ emit_pass "$id" "$parameter" "$after_value" "$live"
470
+ else
471
+ emit_fail "$id" "$parameter" "$after_value" "$live"
472
+ fi
473
+ ;;
474
+ MEM-016)
475
+ local live
476
+ live=$(cat /sys/kernel/mm/ksm/pages_to_scan 2>/dev/null || echo "not available")
477
+ if [[ "$live" == "$after_value" ]]; then
478
+ emit_pass "$id" "$parameter" "$after_value" "$live"
479
+ else
480
+ emit_fail "$id" "$parameter" "$after_value" "$live"
481
+ fi
482
+ ;;
483
+ MEM-017)
484
+ local live
485
+ live=$(cat /sys/kernel/mm/ksm/sleep_millisecs 2>/dev/null || echo "not available")
486
+ if [[ "$live" == "$after_value" ]]; then
487
+ emit_pass "$id" "$parameter" "$after_value" "$live"
488
+ else
489
+ emit_fail "$id" "$parameter" "$after_value" "$live"
490
+ fi
491
+ ;;
492
+ MEM-018)
493
+ local live
494
+ live=$(cat /sys/kernel/mm/ksm/use_zero_pages 2>/dev/null || echo "not available")
495
+ if [[ "$live" == "$after_value" ]]; then
496
+ emit_pass "$id" "$parameter" "$after_value" "$live"
497
+ else
498
+ emit_fail "$id" "$parameter" "$after_value" "$live"
499
+ fi
500
+ ;;
501
+ esac
502
+ }
503
+
504
+ # -------------------------------------------------------
505
+ # Check /etc/environment entries (MEM-019, BUILD-001)
506
+ # -------------------------------------------------------
507
+ check_environment_entry() {
508
+ local id="$1" parameter="$2" after_value="$3"
509
+ local env_file="/etc/environment"
510
+
511
+ if [[ ! -f "$env_file" ]]; then
512
+ emit_fail "$id" "$parameter" "$after_value" "file_missing"
513
+ return
514
+ fi
515
+
516
+ case "$id" in
517
+ MEM-019)
518
+ # LD_PRELOAD jemalloc
519
+ local actual
520
+ actual=$(grep '^LD_PRELOAD=' "$env_file" 2>/dev/null | cut -d= -f2- || echo "")
521
+ if [[ -z "$actual" ]]; then
522
+ actual="not_set"
523
+ fi
524
+ if [[ "$actual" == "$after_value" ]]; then
525
+ emit_pass "$id" "$parameter" "$after_value" "$actual"
526
+ elif echo "$actual" | grep -q 'libjemalloc'; then
527
+ emit_pass "$id" "$parameter" "$after_value" "$actual"
528
+ else
529
+ emit_fail "$id" "$parameter" "$after_value" "$actual"
530
+ fi
531
+ ;;
532
+ BUILD-001)
533
+ # RUSTC_WRAPPER sccache
534
+ local actual
535
+ actual=$(grep '^RUSTC_WRAPPER=' "$env_file" 2>/dev/null | cut -d= -f2- || echo "")
536
+ if [[ -z "$actual" ]]; then
537
+ actual="not_set"
538
+ fi
539
+ if [[ "$actual" == "$after_value" ]]; then
540
+ emit_pass "$id" "$parameter" "$after_value" "$actual"
541
+ else
542
+ emit_fail "$id" "$parameter" "$after_value" "$actual"
543
+ fi
544
+ ;;
545
+ esac
546
+ }
547
+
548
+ # -------------------------------------------------------
549
+ # Check systemd slice entries (CGRP-001..009)
550
+ # Check the unit file for expected property values
551
+ # -------------------------------------------------------
552
+ check_slice_entry() {
553
+ local id="$1" parameter="$2" config_file="$3" after_value="$4"
554
+
555
+ if [[ ! -f "$config_file" ]]; then
556
+ emit_fail "$id" "$parameter" "$after_value" "file_missing"
557
+ return
558
+ fi
559
+
560
+ local content
561
+ content=$(cat "$config_file" 2>/dev/null || echo "")
562
+
563
+ # Extract the property name from the parameter description
564
+ # e.g. "droid.slice CPUWeight" → look for CPUWeight=
565
+ local prop_name=""
566
+ case "$id" in
567
+ CGRP-001) prop_name="CPUWeight" ;;
568
+ CGRP-002) prop_name="MemoryHigh" ;;
569
+ CGRP-003) prop_name="IOWeight" ;;
570
+ CGRP-004) prop_name="ManagedOOMSwap" ;;
571
+ CGRP-005) prop_name="ManagedOOMMemoryPressure" ;;
572
+ CGRP-006) prop_name="ManagedOOMMemoryPressureLimit" ;;
573
+ CGRP-007) prop_name="CPUWeight" ;;
574
+ CGRP-008) prop_name="MemoryHigh" ;;
575
+ CGRP-009) prop_name="IOWeight" ;;
576
+ esac
577
+
578
+ local actual_value
579
+ actual_value=$(echo "$content" | grep "^${prop_name}=" 2>/dev/null | cut -d= -f2- || echo "")
580
+
581
+ if [[ -z "$actual_value" ]]; then
582
+ emit_fail "$id" "$parameter" "$after_value" "property_not_found"
583
+ elif [[ "$actual_value" == "$after_value" ]]; then
584
+ emit_pass "$id" "$parameter" "$after_value" "$actual_value"
585
+ else
586
+ emit_fail "$id" "$parameter" "$after_value" "$actual_value"
587
+ fi
588
+ }
589
+
590
+ # -------------------------------------------------------
591
+ # Check helper script entries (CGRP-010, CGRP-011)
592
+ # Check that OOMScoreAdjust value is in the script
593
+ # -------------------------------------------------------
594
+ check_helper_script() {
595
+ local id="$1" parameter="$2" config_file="$3" after_value="$4"
596
+
597
+ if [[ ! -f "$config_file" ]]; then
598
+ emit_fail "$id" "$parameter" "$after_value" "file_missing"
599
+ return
600
+ fi
601
+
602
+ local content
603
+ content=$(cat "$config_file" 2>/dev/null || echo "")
604
+
605
+ if echo "$content" | grep -q "OOMScoreAdjust=$after_value"; then
606
+ emit_pass "$id" "$parameter" "$after_value" "$after_value"
607
+ else
608
+ local found
609
+ found=$(echo "$content" | grep -oE 'OOMScoreAdjust=-?[0-9]+' | head -1 | cut -d= -f2 || echo "not found")
610
+ emit_fail "$id" "$parameter" "$after_value" "$found"
611
+ fi
612
+ }
613
+
614
+ # -------------------------------------------------------
615
+ # Main: iterate over all manifest entries
616
+ # -------------------------------------------------------
617
+
618
+ # Extract all entries as tab-separated values in one jq call
619
+ tsv_data=$(jq -r '.tuning_entries[] | [.id, .category, .parameter, .config_file, .before_value, .after_value] | @tsv' "$MANIFEST")
620
+
621
+ while IFS=$'\t' read -r id category parameter config_file before_value after_value; do
622
+ case "$config_file" in
623
+ /etc/sysctl.d/99-performance.conf)
624
+ # sysctl entries: read live value from /proc/sys
625
+ check_sysctl "$id" "$parameter" "$after_value" "$config_file"
626
+ ;;
627
+
628
+ "systemctl disable/mask")
629
+ # Service disable/mask entries
630
+ check_service_disabled "$id" "$parameter"
631
+ ;;
632
+
633
+ "systemctl")
634
+ # Service active check (CPU-003 irqbalance)
635
+ check_service_active "$id" "$parameter" "$after_value"
636
+ ;;
637
+
638
+ /etc/fstab)
639
+ check_fstab_entry "$id" "$parameter" "$after_value"
640
+ ;;
641
+
642
+ /etc/udev/rules.d/*)
643
+ check_udev_entry "$id" "$parameter" "$config_file" "$after_value"
644
+ ;;
645
+
646
+ /etc/modules-load.d/*)
647
+ check_modules_load "$id" "$parameter" "$config_file" "$after_value"
648
+ ;;
649
+
650
+ /etc/default/grub)
651
+ check_grub_entry "$id" "$parameter" "$after_value"
652
+ ;;
653
+
654
+ /etc/tmpfiles.d/thp.conf)
655
+ check_thp_entry "$id" "$parameter" "$after_value"
656
+ ;;
657
+
658
+ /etc/tmpfiles.d/ksm.conf)
659
+ check_ksm_entry "$id" "$parameter" "$after_value"
660
+ ;;
661
+
662
+ /etc/environment)
663
+ check_environment_entry "$id" "$parameter" "$after_value"
664
+ ;;
665
+
666
+ /etc/systemd/system/*.slice)
667
+ check_slice_entry "$id" "$parameter" "$config_file" "$after_value"
668
+ ;;
669
+
670
+ /usr/local/bin/*)
671
+ check_helper_script "$id" "$parameter" "$config_file" "$after_value"
672
+ ;;
673
+
674
+ *)
675
+ emit_fail "$id" "$parameter" "$after_value" "unknown_config_type:$config_file"
676
+ ;;
677
+ esac
678
+ done <<< "$tsv_data"
679
+
680
+ # -------------------------------------------------------
681
+ # Summary
682
+ # -------------------------------------------------------
683
+ echo ""
684
+ echo "${PASS_COUNT}/${TOTAL} checks passed"
685
+
686
+ if [[ $FAIL_COUNT -eq 0 && $SKIP_COUNT -eq 0 ]]; then
687
+ exit 0
688
+ elif [[ $FAIL_COUNT -gt 0 ]]; then
689
+ exit 1
690
+ else
691
+ # Only SKIPs, no FAILs — still consider success
692
+ exit 0
693
+ fi