Menghapus file adalah bagian pemeliharaan yang paling tidak bisa diulang: file yang salah terhapus tidak punya undo. Karena itu bagian ini justru dimulai dari aturan, bukan dari perintah — tiga pengaman yang membuat pembersihan bisa dipertanggungjawabkan bahkan saat dijadwalkan tiap minggu. Setelah aturannya jelas, kita menentukan umur file dengan tanggal acuan yang bisa dikunci, lalu menjalankan pembersihan dalam dua tahap: dry-run menampilkan daftar dulu, --apply baru benar-benar menghapus.
Tiga Pengaman Sebelum Menghapus #
Pengaman pertama adalah whitelist: daftar folder yang boleh dibersihkan, ditulis di berkas konfigurasi, dan find hanya boleh berangkat dari folder dalam daftar itu. File di luar whitelist tidak “juga ikut dibersihkan” — ia tidak pernah dicari sekalipun. Pengaman kedua adalah umur: hanya file yang lebih tua dari batas per jenisnya, jadi file yang masih aktif tidak tersentuh. Pengaman ketiga adalah dry-run sebagai default: skrip menampilkan apa yang akan dihapus, dan tidak menjalankan penghapusan sampai --apply diketik sendiri oleh manusia yang membaca daftarnya.
Whitelist dan Umur di Konfigurasi #
Seluruh keputusan pembersihan hidup di pemeliharaan.conf, bukan di dalam skrip — pengguna cukup menyunting nilai tanpa menyentuh logika:
# pemeliharaan.conf — konfigurasi skrip pemeliharaan.
# Sunting nilai di bawah ini sesuai komputer Anda; skrip pemeliharaan.sh
# tidak perlu disentuh. Jalur whitelist dihitung dari direktori kerja
# saat skrip dijalankan; jalur absolut (mis. /var/cache/myapp) juga
# boleh dipakai.
# Whitelist folder yang BOLEH dibersihkan, dipisah spasi.
# Folder di luar tiga daftar ini tidak pernah disentuh skrip.
CACHE_DIRS="data/cache"
LOG_DIRS="data/log"
TMP_DIRS="data/tmp"
# Umur maksimum file (hari) sebelum layak dibersihkan.
CACHE_MAX_DAYS=30
LOG_MAX_DAYS=14
TMP_MAX_DAYS=7
# Ambang pemakaian disk (%) yang menyalakan status WASPADA (1-99).
DISK_WARN_PCT=80
# Berkas laporan (opsional; default: laporan-pemeliharaan.txt).
REPORT_FILE="laporan-pemeliharaan.txt"Enam variabel pertama adalah inti whitelist: tiga jenis sumber (CACHE_DIRS, LOG_DIRS, TMP_DIRS) masing-masing dengan batas umurnya sendiri. Jenisnya berbeda perlakuan: cache dan tmp dibersihkan tanpa saringan nama, sedangkan log hanya file terkompresi *.gz — file app.log yang masih ditulis aplikasi tidak boleh dihapus, sementara file .gz justru memang buatan rotasi log yang sudah tua. Ambang disk dipakai cek dari Bagian 1, dan REPORT_FILE menentukan tujuan laporan Bagian 3.
Perhatikan juga apa yang tidak ada di berkas itu: mode. Tidak ada baris MODE="apply", dan itu disengaja. Skrip menetapkan mode setelah konfigurasi dibaca — walau seseorang menambahkan baris MODE=apply ke konfigurasi, nilainya tetap ditimpa kembali ke dry-run. Pembersihan sungguhan hanya menyala lewat argumen --apply yang harus diketik pada eksekusi itu juga, sehingga tidak ada konfigurasi yang terlupa membuat skrip terjadwal menjadi perusak diam-diam.
Umur File: -mtime atau Tanggal Acuan #
Cara klasik mencari file tua adalah find -mtime +30 — “lebih tua dari 30 hari”. Cara itu benar, tapi dihitung dari jam eksekusi: file yang sama bisa jadi kandidat hari ini dan tidak besok, sehingga hasilnya sulit diuji dan sulit dibandingkan antar mesin. Skrip kita memakai ! -newermt dengan batas tanggal yang dihitung dari tanggal acuan:
| Aspek | -mtime +30 | ! -newermt "$batas" |
|---|---|---|
| Titik acuan | jam eksekusi find | tanggal acuan HARI_INI |
| Menentukan batas | langsung, satu tes | hitung dulu dengan date -d |
| Hasil antar eksekusi | bergeser mengikuti waktu | identik selama HARI_INI sama |
| Cocok untuk | pembersihan sekali jalan | skrip teruji dan terjadwal |
Variabel HARI_INI default mengambil tanggal hari ini, jadi di pemakaian normal keduanya setara. Saat menguji, kita menguncinya ke tanggal tetap — dan seluruh umur file menjadi deterministik. Cara kerjanya kita lihat langsung:
mkdir -p data/cache
printf 'isi cache lama\n' > data/cache/lama.txt
printf 'isi cache baru\n' > data/cache/baru.txt
touch -d "2026-01-10" data/cache/lama.txt
touch -d "2026-02-10" data/cache/baru.txt
HARI_INI=2026-02-15
batas=$(date -d "$HARI_INI -30 days" +%F)
echo "tanggal acuan : $HARI_INI"
echo "batas umur : $batas (cache 30 hari)"
echo "== semua file cache =="
ls -1 data/cache
echo "== kandidat: lebih tua dari batas =="
find data/cache -type f ! -newermt "$batas" | sortOutput yang Dihasilkan:
tanggal acuan : 2026-02-15
batas umur : 2026-01-16 (cache 30 hari)
== semua file cache ==
baru.txt
lama.txt
== kandidat: lebih tua dari batas ==
data/cache/lama.txtBedah baris-per-baris:
touch -d "2026-01-10": menandai mtime file ke tanggal itu — inilah cara membekukan umur file di fixture, sama seperti snapshot membekukan output perintah di Bagian 1.date -d "$HARI_INI -30 days" +%F:datemenerima tanggal lalu aritmetika hari, dan%Fmencetak hasilnya sebagaiYYYY-MM-DD— dari2026-02-15dan umur 30 hari lahir batas2026-01-16.find ... ! -newermt "$batas": file yang mtime-nya tidak lebih baru dari batas — artinya sama tua atau lebih tua — masuk daftar kandidat. Filebaru.txt(2026-02-10) lebih baru dari2026-01-16, jadi tersingkir;lama.txt(2026-01-10) lolos.| sort: urutanfindmengikuti isi direktori yang bisa berbeda antar mesin —sortmembuat daftar kandidat selalu terurut sama.
Dry-Run Dulu, Baru Apply #
Pola lengkapnya kini bisa kita jalankan pada tiga whitelist sekaligus. Bagian pertama blok berikut adalah dry-run versi panjang: find dengan -printf menampilkan kandidat beserta ukurannya tanpa menghapus apa pun. Baris terakhir menguji betapa pentingnya pola *.gz: tanpa pola itu, app.log yang sama-sama tua akan ikut terdaftar. Bagian kedua adalah apply dengan -delete, ditutup bukti ls sebelum dan sesudah:
mkdir -p data/cache data/log data/tmp
printf 'isi cache lama\n' > data/cache/lama.txt; touch -d "2026-01-10" data/cache/lama.txt
printf 'isi cache baru\n' > data/cache/baru.txt; touch -d "2026-02-10" data/cache/baru.txt
printf 'isi log lama terkompresi\n' > data/log/app.log.1.gz; touch -d "2026-01-25" data/log/app.log.1.gz
printf 'isi log lama tapi bukan gz\n' > data/log/app.log; touch -d "2026-01-20" data/log/app.log
printf 'isi log baru\n' > data/log/app.log.2.gz; touch -d "2026-02-12" data/log/app.log.2.gz
printf 'sisa instalasi lama\n' > data/tmp/sisa.tmp; touch -d "2026-02-01" data/tmp/sisa.tmp
printf 'file tmp aktif\n' > data/tmp/aktif.tmp; touch -d "2026-02-13" data/tmp/aktif.tmp
batas_cache=$(date -d "2026-02-15 -30 days" +%F)
batas_log=$(date -d "2026-02-15 -14 days" +%F)
batas_tmp=$(date -d "2026-02-15 -7 days" +%F)
echo "== DRY-RUN: kandidat cache (lebih tua dari $batas_cache) =="
find data/cache -type f ! -newermt "$batas_cache" -printf ' akan dihapus: %p (%s byte)\n' | sort
echo "== DRY-RUN: kandidat log *.gz (lebih tua dari $batas_log) =="
find data/log -type f -name '*.gz' ! -newermt "$batas_log" -printf ' akan dihapus: %p (%s byte)\n' | sort
echo "== DRY-RUN: kandidat tmp (lebih tua dari $batas_tmp) =="
find data/tmp -type f ! -newermt "$batas_tmp" -printf ' akan dihapus: %p (%s byte)\n' | sort
echo "== semua file log tua (tanpa pola *.gz) =="
find data/log -type f ! -newermt "$batas_log" -printf '%p\n' | sort
echo "== APPLY: hapus kandidat =="
find data/cache -type f ! -newermt "$batas_cache" -delete
find data/log -type f -name '*.gz' ! -newermt "$batas_log" -delete
find data/tmp -type f ! -newermt "$batas_tmp" -delete
echo "== kondisi akhir =="
ls -1 data/cache data/log data/tmpOutput yang Dihasilkan:
== DRY-RUN: kandidat cache (lebih tua dari 2026-01-16) ==
akan dihapus: data/cache/lama.txt (15 byte)
== DRY-RUN: kandidat log *.gz (lebih tua dari 2026-02-01) ==
akan dihapus: data/log/app.log.1.gz (25 byte)
== DRY-RUN: kandidat tmp (lebih tua dari 2026-02-08) ==
akan dihapus: data/tmp/sisa.tmp (20 byte)
== semua file log tua (tanpa pola *.gz) ==
data/log/app.log
data/log/app.log.1.gz
== APPLY: hapus kandidat ==
== kondisi akhir ==
data/cache:
baru.txt
data/log:
app.log
app.log.2.gz
data/tmp:
aktif.tmpHasil akhirnya adalah bukti yang ingin kita lihat setiap kali mode apply dijalankan:
-printf ' akan dihapus: %p (%s byte)\n':findsendiri sanggup mencetak jalur (%p) dan ukuran (%s) — daftar dry-run jadi informatif tanpa loop tambahan.-name '*.gz'sebelum! -newermt: urutan tes tidak mengubah hasil, tapi pola nama mempersempit ruang cari lebih dulu —app.logyang sama-sama tua denganapp.log.1.gztidak pernah jadi kandidat.-delete: varian penghapusan langsungfind— aman karena mewarisi seluruh saringan tes yang sama (whitelist,-type f, umur, pola) dalam satu perintah; rincian opsinya di find(1) .lspembuktian: cache menyisakanbaru.txt, log menyisakanapp.logdanapp.log.2.gz, tmp menyisakanaktif.tmp— tepat tiga file tua yang hilang, nol file sehat yang ikut.
Kalau pola seperti ini terasa familiar, memang begitu: find dengan xargs dan ekspresi majemuknya sudah kita bedah di Chapter find dan xargs
, dan file .gz yang kita bersihkan adalah sisa rotasi log dari Chapter Logs dan Log Rotation
. Bedanya, di sini seluruh keputusan datang dari konfigurasi dan seluruh penghapusan tercatat ukurannya — dua hal yang kita butuhkan untuk laporan pada bagian penutup proyek.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
src/pemeliharaan.sh
Unduh berkas#!/usr/bin/env bash
# pemeliharaan.sh — pemeliharaan berkala: periksa, bersihkan, laporkan.
#
# Membaca snapshot data sistem (df.txt, paket.txt, services.txt) dari
# folder data, memeriksa tiga hal — pemakaian disk, paket update
# tertunda, service gagal — lalu membersihkan file cache, log terkompresi
# (*.gz), dan file tmp usang HANYA di dalam folder whitelist yang
# tercantum di berkas konfigurasi. Mode default DRY-RUN: tidak ada
# file yang dihapus sampai --apply diberikan, dan mode tidak bisa
# diubah dari konfigurasi. Hasil akhirnya adalah laporan terstruktur
# yang ditulis ke stdout sekaligus berkas laporan.
#
# Pemakaian:
# bash pemeliharaan.sh [--data-dir DIR] [--config FILE] [--apply]
#
# Variabel lingkungan (berguna untuk pengujian):
# CONF berkas konfigurasi yang dibaca (default: pemeliharaan.conf)
# SOURCE_DIR folder data; kalah oleh --data-dir (default: data)
# HARI_INI tanggal acuan umur file, format YYYY-MM-DD (default: hari ini)
#
# Kode keluar:
# 0 sukses 1 konfigurasi tidak valid
# 2 berkas snapshot tidak ada 3 folder whitelist tidak ada
set -u
CONF="${CONF:-pemeliharaan.conf}"
DATA_DIR="${SOURCE_DIR:-data}"
HARI_INI="${HARI_INI:-$(date +%F)}"
APPLY=0
# --- 1. Baca argumen -----------------------------------------------------
while [ $# -gt 0 ]; do
case "$1" in
--apply) APPLY=1; shift ;;
--data-dir|--config)
if [ $# -lt 2 ]; then
printf 'pemeliharaan.sh: %s butuh nilai\n' "$1" >&2
exit 1
fi
if [ "$1" = "--data-dir" ]; then
DATA_DIR="$2"
else
CONF="$2"
fi
shift 2
;;
*)
printf 'pemeliharaan.sh: argumen tidak dikenal: %s\n' "$1" >&2
exit 1
;;
esac
done
# --- 2. Baca dan validasi konfigurasi ------------------------------------
if [ ! -f "$CONF" ]; then
printf 'pemeliharaan.sh: berkas konfigurasi %s tidak ditemukan\n' "$CONF" >&2
exit 1
fi
# shellcheck source=/dev/null
. "$CONF"
if [ -z "${CACHE_DIRS:-}" ] && [ -z "${LOG_DIRS:-}" ] && [ -z "${TMP_DIRS:-}" ]; then
printf 'pemeliharaan.sh: whitelist kosong (CACHE_DIRS/LOG_DIRS/TMP_DIRS) di %s\n' "$CONF" >&2
exit 1
fi
valid_umur() {
case $1 in
''|*[!0-9]*|0) return 1 ;;
esac
return 0
}
valid_umur "${CACHE_MAX_DAYS:-}" || {
printf 'pemeliharaan.sh: CACHE_MAX_DAYS harus angka 1 atau lebih\n' >&2; exit 1; }
valid_umur "${LOG_MAX_DAYS:-}" || {
printf 'pemeliharaan.sh: LOG_MAX_DAYS harus angka 1 atau lebih\n' >&2; exit 1; }
valid_umur "${TMP_MAX_DAYS:-}" || {
printf 'pemeliharaan.sh: TMP_MAX_DAYS harus angka 1 atau lebih\n' >&2; exit 1; }
case ${DISK_WARN_PCT:-} in
''|*[!0-9]*|0)
printf 'pemeliharaan.sh: DISK_WARN_PCT harus angka 1-99\n' >&2
exit 1
;;
esac
if [ "$DISK_WARN_PCT" -gt 99 ]; then
printf 'pemeliharaan.sh: DISK_WARN_PCT harus angka 1-99\n' >&2
exit 1
fi
# Mode sengaja di set SETELAH konfigurasi dibaca: walau berkas
# konfigurasi berisi "MODE=apply", nilainya tetap ditimpa di sini.
# Hanya argumen --apply yang boleh menyalakan pembersihan sungguhan.
MODE="dry-run"
if [ "$APPLY" -eq 1 ]; then
MODE="apply"
fi
# --- 3. Validasi snapshot data --------------------------------------------
for f in df.txt paket.txt services.txt; do
if [ ! -f "$DATA_DIR/$f" ]; then
printf 'pemeliharaan.sh: snapshot %s/%s tidak ditemukan\n' "$DATA_DIR" "$f" >&2
exit 2
fi
done
# --- 4. Pemeriksaan -------------------------------------------------------
# Temuan pemeriksaan terkumpul di sini, lalu menjadi bagian laporan.
# Disk: kolom ke-5 df adalah Use%; "+0" di awk membuang karakter '%'
# sehingga "89%" terbaca sebagai angka 89.
DISK_BARIS=$(awk -v ambang="$DISK_WARN_PCT" '
NR > 1 {
pct = $5 + 0
if (pct >= ambang)
printf " %-10s %3d%% WASPADA\n", $6, pct
else
printf " %-10s %3d%% OK\n", $6, pct
}' "$DATA_DIR/df.txt")
DISK_MELEBIHI=$(awk -v ambang="$DISK_WARN_PCT" '
NR > 1 && ($5 + 0) >= ambang {
printf "%s%s", pisah, $6
pisah = ", "
}' "$DATA_DIR/df.txt")
DISK_N=$(awk -v ambang="$DISK_WARN_PCT" '
NR > 1 && ($5 + 0) >= ambang { n++ } END { print n + 0 }' "$DATA_DIR/df.txt")
# Paket tertunda: output simulasi apt-get -s upgrade menandai tiap paket
# dengan satu baris "Inst <nama> [versi lama] (versi baru)".
PAKET_DAFTAR=$(awk '/^Inst / {
printf "%s%s", pisah, $2
pisah = ", "
}' "$DATA_DIR/paket.txt")
PAKET_N=$(awk '/^Inst / { n++ } END { print n + 0 }' "$DATA_DIR/paket.txt")
# Service gagal: snapshot berformat "unit substate" per baris.
SVC_DAFTAR=$(awk '$2 == "failed" {
printf "%s%s", pisah, $1
pisah = ", "
}' "$DATA_DIR/services.txt")
SVC_N=$(awk '$2 == "failed" { n++ } END { print n + 0 }' "$DATA_DIR/services.txt")
# --- 5. Pembersihan (hanya di dalam whitelist) -----------------------------
# Hasil pembersihan terkumpul di BAG_BERSIH; penghitung JUMLAH_FILE dan
# HEMAT_BYTES dipakai seksi HEMAT RUANG di laporan.
JUMLAH_FILE=0
HEMAT_BYTES=0
BAG_BERSIH=""
# bersihkan <judul> <dirs> <umur-hari> <pola-nama>:
# kumpulkan file di folder whitelist yang lebih tua dari batas umur,
# catat ukurannya untuk laporan, dan hapus hanya bila mode apply.
# -type f menjaga folder tetap utuh; find hanya berangkat dari folder
# whitelist, jadi file di luar daftar tidak pernah tersentuh.
bersihkan() {
judul="$1"
dirs="$2"
umur="$3"
pola="${4:-*}"
batas=$(date -d "$HARI_INI -$umur days" +%F)
if [ "$pola" = "*" ]; then
label="file"
else
label="file $pola"
fi
terpakai=0
if [ -n "$BAG_BERSIH" ]; then
BAG_BERSIH+=$'\n'
fi
BAG_BERSIH+="$judul — $label lebih tua dari $batas (whitelist: $dirs)"$'\n'
# shellcheck disable=SC2086 # whitelist memang dipisah spasi
for dir in $dirs; do
if [ ! -d "$dir" ]; then
printf 'pemeliharaan.sh: folder whitelist %s tidak ditemukan\n' "$dir" >&2
exit 3
fi
# Process substitution < <(...) menjaga penghitung tetap hidup
# di shell utama; pipeline biasa akan menjalankan while di
# subshell sehingga JUMLAH_FILE hilang.
while IFS= read -r f; do
[ -n "$f" ] || continue
ukuran=$(stat -c %s "$f")
JUMLAH_FILE=$((JUMLAH_FILE + 1))
HEMAT_BYTES=$((HEMAT_BYTES + ukuran))
terpakai=$((terpakai + 1))
if [ "$MODE" = "apply" ]; then
rm -f -- "$f"
BAG_BERSIH+=" [terhapus] $f (${ukuran} byte)"$'\n'
else
BAG_BERSIH+=" [dry-run] $f (${ukuran} byte)"$'\n'
fi
done < <(find "$dir" -type f -name "$pola" ! -newermt "$batas" | sort)
done
if [ "$terpakai" -eq 0 ]; then
BAG_BERSIH+=" (tidak ada kandidat)"$'\n'
fi
}
if [ -n "${CACHE_DIRS:-}" ]; then
bersihkan "cache" "$CACHE_DIRS" "$CACHE_MAX_DAYS"
fi
if [ -n "${LOG_DIRS:-}" ]; then
bersihkan "log" "$LOG_DIRS" "$LOG_MAX_DAYS" "*.gz"
fi
if [ -n "${TMP_DIRS:-}" ]; then
bersihkan "tmp" "$TMP_DIRS" "$TMP_MAX_DAYS"
fi
# --- 6. Susun laporan -----------------------------------------------------
if [ "$MODE" = "apply" ]; then
LABEL_MODE="APPLY (pembersihan benar-benar dijalankan)"
LABEL_HEMAT="benar-benar dibebaskan dari disk"
else
LABEL_MODE="DRY-RUN (tidak ada file yang dihapus)"
LABEL_HEMAT="potensi hemat; jalankan --apply untuk benar-benar menghapus"
fi
LAPORAN=""
tambah() {
LAPORAN+="$1"
LAPORAN+=$'\n'
}
tambah "================================================================"
tambah "LAPORAN PEMELIHARAAN SISTEM"
tambah "Tanggal acuan : $HARI_INI"
tambah "Mode : $LABEL_MODE"
tambah "================================================================"
tambah ""
tambah "PEMERIKSAAN"
tambah "----------------------------------------------------------------"
tambah "1. Pemakaian disk (ambang $DISK_WARN_PCT%)"
tambah "$DISK_BARIS"
tambah "2. Paket update tertunda"
if [ "$PAKET_N" -gt 0 ]; then
tambah " $PAKET_N paket siap di-upgrade: $PAKET_DAFTAR"
else
tambah " tidak ada paket tertunda"
fi
tambah "3. Service gagal"
if [ "$SVC_N" -gt 0 ]; then
tambah " $SVC_N service failed: $SVC_DAFTAR"
else
tambah " semua service berjalan normal"
fi
tambah ""
tambah "PEMBERSIHAN"
tambah "----------------------------------------------------------------"
tambah "${BAG_BERSIH%$'\n'}"
tambah ""
tambah "HEMAT RUANG"
tambah "----------------------------------------------------------------"
tambah "Total: $JUMLAH_FILE file, $HEMAT_BYTES byte ($LABEL_HEMAT)"
tambah ""
tambah "REKOMENDASI"
tambah "----------------------------------------------------------------"
nomor=1
if [ "$DISK_N" -gt 0 ]; then
tambah "$nomor. Pemakaian disk melewati ambang $DISK_WARN_PCT% di $DISK_MELEBIHI:"
tambah " cari penyumbang terbesar dengan du -h, lalu bersihkan atau pindahkan."
nomor=$((nomor + 1))
fi
if [ "$PAKET_N" -gt 0 ]; then
tambah "$nomor. Ada $PAKET_N paket update tertunda:"
tambah " jalankan apt upgrade pada jadwal maintenance berikutnya."
nomor=$((nomor + 1))
fi
if [ "$SVC_N" -gt 0 ]; then
tambah "$nomor. Ada $SVC_N service gagal ($SVC_DAFTAR):"
tambah " periksa journalctl -u <unit> untuk penyebabnya, lalu restart bila aman."
nomor=$((nomor + 1))
fi
if [ "$MODE" = "dry-run" ] && [ "$JUMLAH_FILE" -gt 0 ]; then
tambah "$nomor. DRY-RUN menemukan $JUMLAH_FILE file ($HEMAT_BYTES byte) layak dibersihkan:"
tambah " periksa daftar di bagian PEMBERSIHAN, lalu jalankan ulang dengan --apply."
nomor=$((nomor + 1))
fi
if [ "$nomor" -eq 1 ]; then
tambah "1. Tidak ada temuan: sistem dalam kondisi baik. Ulangi pemeliharaan sesuai jadwal."
fi
# --- 7. Tulis laporan ke stdout dan berkas ---------------------------------
REPORT_FILE="${REPORT_FILE:-laporan-pemeliharaan.txt}"
mkdir -p "$(dirname "$REPORT_FILE")"
printf '%s' "$LAPORAN" | tee "$REPORT_FILE"
exit 0
tests/run-tests.sh
Unduh berkas#!/usr/bin/env bash
# run-tests.sh — test suite untuk pemeliharaan.sh.
# Suite menyiapkan sendiri folder kerja terisolasi (mktemp) beserta
# data contohnya — snapshot beku (df.txt, paket.txt, services.txt) dan
# file cache/log/tmp dengan mtime beku lewat touch -d — lalu menjalankan
# rangkaian pengujian dengan assertion sederhana. Jalankan dari mana
# saja: bash tests/run-tests.sh
set -u
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJ_DIR=$(dirname "$SCRIPT_DIR")
PASS=0
FAIL=0
# assert_eq <harapan> <nyata> <pesan>: bandingkan dua nilai.
assert_eq() {
if [ "$1" = "$2" ]; then
PASS=$((PASS + 1))
printf 'OK %s\n' "$3"
else
FAIL=$((FAIL + 1))
printf 'GAGAL %s: harapan %s, nyata %s\n' "$3" "$1" "$2"
fi
}
# --- siapkan folder kerja terisolasi ------------------------------------
WORK=$(mktemp -d)
cp "$PROJ_DIR/src/pemeliharaan.sh" "$PROJ_DIR/config/pemeliharaan.conf" "$WORK/"
cd "$WORK" || exit 1
# --- fixture: snapshot data beku ----------------------------------------
mkdir -p data/cache data/log data/tmp
cat > data/df.txt <<'EOF'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 35G 13G 73% /
/dev/sdb1 200G 170G 21G 89% /data
tmpfs 7.8G 1.1M 7.8G 1% /dev/shm
EOF
cat > data/paket.txt <<'EOF'
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
Reading state information... Done
The following packages will be upgraded:
curl libcurl4 libssl3 openssl
4 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Inst curl [7.88.1-10] (7.88.1-11)
Inst libcurl4 [7.88.1-10] (7.88.1-11)
Inst libssl3 [3.0.8-1] (3.0.8-2)
Inst openssl [3.0.8-1] (3.0.8-2)
Conf curl (7.88.1-11)
Conf libcurl4 (7.88.1-11)
Conf libssl3 (3.0.8-2)
Conf openssl (3.0.8-2)
EOF
cat > data/services.txt <<'EOF'
nginx.service failed
postgresql.service failed
sshd.service running
cron.service running
EOF
# --- fixture: file dengan mtime beku -------------------------------------
# Tanggal acuan pengujian: 2026-02-15. Batas umur menjadi
# cache 30 hari -> 2026-01-16, log 14 hari -> 2026-02-01,
# tmp 7 hari -> 2026-02-08.
printf 'isi cache lama\n' > data/cache/lama.txt
touch -d "2026-01-10" data/cache/lama.txt
printf 'isi cache baru\n' > data/cache/baru.txt
touch -d "2026-02-10" data/cache/baru.txt
printf 'isi log lama terkompresi\n' > data/log/app.log.1.gz
touch -d "2026-01-25" data/log/app.log.1.gz
printf 'isi log lama tapi bukan gz\n' > data/log/app.log
touch -d "2026-01-20" data/log/app.log
printf 'isi log baru\n' > data/log/app.log.2.gz
touch -d "2026-02-12" data/log/app.log.2.gz
printf 'sisa instalasi lama\n' > data/tmp/sisa.tmp
touch -d "2026-02-01" data/tmp/sisa.tmp
printf 'file tmp aktif\n' > data/tmp/aktif.tmp
touch -d "2026-02-13" data/tmp/aktif.tmp
# Ukuran total yang harus dibebaskan mode apply: tiga file tua.
total_hemat=$(stat -c %s data/cache/lama.txt data/log/app.log.1.gz \
data/tmp/sisa.tmp | awk '{ s += $1 } END { print s }')
# --- uji 1: dry-run sukses dan tidak menghapus ---------------------------
HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 0 "$?" "dry-run sukses keluar 0"
[ -f data/cache/lama.txt ]
assert_eq 0 "$?" "dry-run tidak menghapus cache lama"
[ -f data/log/app.log.1.gz ]
assert_eq 0 "$?" "dry-run tidak menghapus log lama"
# --- uji 2: laporan dibuat dengan empat seksi ----------------------------
[ -f laporan-pemeliharaan.txt ]
assert_eq 0 "$?" "berkas laporan dibuat"
grep -q '^PEMERIKSAAN$' laporan-pemeliharaan.txt
assert_eq 0 "$?" "laporan memuat seksi PEMERIKSAAN"
grep -q '^PEMBERSIHAN$' laporan-pemeliharaan.txt
assert_eq 0 "$?" "laporan memuat seksi PEMBERSIHAN"
grep -q '^HEMAT RUANG$' laporan-pemeliharaan.txt
assert_eq 0 "$?" "laporan memuat seksi HEMAT RUANG"
grep -q '^REKOMENDASI$' laporan-pemeliharaan.txt
assert_eq 0 "$?" "laporan memuat seksi REKOMENDASI"
# --- uji 3: parsing ketiga pemeriksaan benar ------------------------------
grep -q '89% WASPADA' laporan-pemeliharaan.txt
assert_eq 0 "$?" "disk 89% ditandai WASPADA"
grep -q '73% OK' laporan-pemeliharaan.txt
assert_eq 0 "$?" "disk 73% ditandai OK"
grep -q '4 paket siap di-upgrade: curl, libcurl4, libssl3, openssl' \
laporan-pemeliharaan.txt
assert_eq 0 "$?" "jumlah dan daftar paket tertunda benar"
grep -q '2 service failed: nginx.service, postgresql.service' \
laporan-pemeliharaan.txt
assert_eq 0 "$?" "jumlah dan daftar service gagal benar"
# --- uji 4: daftar kandidat dan rekomendasi dry-run ------------------------
grep -Fq '[dry-run] data/cache/lama.txt' laporan-pemeliharaan.txt
assert_eq 0 "$?" "dry-run menampilkan kandidat cache"
grep -Fq '[dry-run] data/log/app.log.1.gz' laporan-pemeliharaan.txt
assert_eq 0 "$?" "dry-run menampilkan kandidat log"
grep -Fq '[dry-run] data/tmp/sisa.tmp' laporan-pemeliharaan.txt
assert_eq 0 "$?" "dry-run menampilkan kandidat tmp"
grep -q 'jalankan ulang dengan --apply' laporan-pemeliharaan.txt
assert_eq 0 "$?" "rekomendasi menyarankan --apply"
# --- uji 5: dua run dry-run menghasilkan laporan identik -------------------
cp laporan-pemeliharaan.txt laporan-run1.txt
HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 0 "$?" "dry-run kedua sukses keluar 0"
diff laporan-run1.txt laporan-pemeliharaan.txt > /dev/null 2>&1
assert_eq 0 "$?" "dua laporan dry-run identik"
# --- uji 6: apply menghapus yang benar saja -------------------------------
HARI_INI=2026-02-15 bash pemeliharaan.sh --apply > /dev/null 2>&1
assert_eq 0 "$?" "apply sukses keluar 0"
[ ! -f data/cache/lama.txt ]
assert_eq 0 "$?" "apply menghapus cache lama"
[ ! -f data/log/app.log.1.gz ]
assert_eq 0 "$?" "apply menghapus log gz lama"
[ ! -f data/tmp/sisa.tmp ]
assert_eq 0 "$?" "apply menghapus tmp lama"
[ -f data/cache/baru.txt ]
assert_eq 0 "$?" "cache baru tetap ada"
[ -f data/log/app.log ]
assert_eq 0 "$?" "log lama non-gz tetap ada (pola hanya *.gz)"
[ -f data/log/app.log.2.gz ]
assert_eq 0 "$?" "log gz baru tetap ada"
[ -f data/tmp/aktif.tmp ]
assert_eq 0 "$?" "tmp baru tetap ada"
# --- uji 7: laporan apply akurat -------------------------------------------
terhapus=$(grep -Fc '[terhapus]' laporan-pemeliharaan.txt)
assert_eq 3 "$terhapus" "laporan apply mencatat 3 file terhapus"
grep -q "Total: 3 file, $total_hemat byte (benar-benar dibebaskan dari disk)" \
laporan-pemeliharaan.txt
assert_eq 0 "$?" "total hemat ruang cocok dengan jumlah byte fixture"
! grep -q 'jalankan ulang dengan --apply' laporan-pemeliharaan.txt
assert_eq 0 "$?" "laporan apply tidak menyarankan --apply lagi"
# --- uji 8: exit code untuk tiap jenis kegagalan ----------------------------
CONF=nihil.conf HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 1 "$?" "konfigurasi hilang keluar 1"
printf 'CACHE_DIRS="data/cache"\nLOG_DIRS=""\nTMP_DIRS=""\nCACHE_MAX_DAYS=abc\nLOG_MAX_DAYS=14\nTMP_MAX_DAYS=7\nDISK_WARN_PCT=80\n' > umur-rusak.conf
CONF=umur-rusak.conf HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 1 "$?" "umur tidak angka keluar 1"
mkdir -p data-kosong
SOURCE_DIR=data-kosong HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 2 "$?" "snapshot hilang keluar 2"
printf 'CACHE_DIRS="data/cache"\nLOG_DIRS="data/log"\nTMP_DIRS="data/tidak-ada"\nCACHE_MAX_DAYS=30\nLOG_MAX_DAYS=14\nTMP_MAX_DAYS=7\nDISK_WARN_PCT=80\n' > whitelist-rusak.conf
CONF=whitelist-rusak.conf HARI_INI=2026-02-15 bash pemeliharaan.sh > /dev/null 2>&1
assert_eq 3 "$?" "folder whitelist hilang keluar 3"
HARI_INI=2026-02-15 bash pemeliharaan.sh --bogus > /dev/null 2>&1
assert_eq 1 "$?" "argumen tak dikenal keluar 1"
# --- ringkasan --------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
printf 'PASSED: %d/%d test lulus\n' "$PASS" "$PASS"
else
printf 'FAILED: %d/%d test lulus\n' "$PASS" "$((PASS + FAIL))"
fi
cd / && rm -rf "$WORK"
[ "$FAIL" -eq 0 ]