Pembersihan yang cerdas selalu berdiri di atas data, bukan firasat. Sebelum memutuskan file mana yang boleh dihapus, kita perlu tahu tiga hal: apakah disk mulai penuh, apakah ada paket update yang tertunda, dan apakah ada service yang sedang gagal. Ketiganya adalah gejala klasik sistem yang tidak dirawat, dan ketiganya bisa diukur. Pada bagian ini kita membangun fondasi proyek: snapshot data yang beku, tiga cek yang memparsenya, dan status OK atau WASPADA yang menjadi bahasa komunikasi untuk dua bagian berikutnya.
Tiga Pemeriksaan Pembuka #
Setiap eksekusi pemeliharaan kita mulai dengan tiga pertanyaan yang sama. Tabel berikut memetakan pertanyaan itu ke sumber datanya:
| Pemeriksaan | Snapshot | Perintah yang menghasilkannya | Sinyal WASPADA |
|---|---|---|---|
| Pemakaian disk | data/df.txt | df -h | persentase pemakaian ≥ ambang |
| Paket tertunda | data/paket.txt | apt-get -s upgrade | ada baris Inst |
| Service gagal | data/services.txt | systemctl list-units --type=service | ada state failed |
Pemakaian disk memberi tahu apakah pembersihan bahkan diperlukan; paket tertunda menandakan sistem yang lama tidak di-update; service gagal adalah masalah yang perlu ditindaklanjuti manual karena skrip pemeliharaan kita sengaja tidak menyentuh service milik orang lain. Ketiganya berbeda sifat — satu angka, satu hitungan, satu daftar — dan itu membuat latihan parsing-nya pun beragam.
Pola Snapshot: Bekukan Data Dulu, Baru Analisis #
Skrip kita tidak menjalankan df atau apt-get secara langsung, melainkan membaca snapshot dari folder data/. Keputusan ini punya tiga alasan praktis. Pertama, hasilnya bisa diulang: snapshot yang sama menghasilkan laporan yang sama, sehingga perilaku skrip bisa diuji otomatis tanpa bergantung pada kondisi mesin. Kedua, bisa diaudit: saat laporan menulis “89%”, kita bisa membuka data/df.txt dan melihat sendiri dari mana angka itu berasal. Ketiga, aman: apt-get -s upgrade memang simulasi, tapi tetap butuh membaca index paket yang isinya berbeda antar mesin — snapshot memisahkan pengumpulan dari analisis.
Di komputer Anda, snapshot dibuat dengan tiga perintah berikut sebelum skrip dijalankan:
mkdir -p data
df -h > data/df.txt
apt-get -s upgrade > data/paket.txt 2> /dev/null
systemctl list-units --type=service --plain --no-legend \
| awk '{ print $1, $4 }' > data/services.txtOpsi -s pada apt-get -s upgrade membuat apt hanya mensimulasikan — tidak ada paket yang diunduh atau dipasang, dan tidak butuh root; strukturnya didokumentasikan di apt-get(8)
. Kolom yang kita ambil dari systemctl adalah nama unit dan substate, sehingga services.txt berformat satu baris per service: unit state. Untuk sesi latihan di proyek ini, kita menulis ketiga snapshot secara manual dengan data server-kantor yang kondisinya sengaja dibuat bervariasi:
mkdir -p data
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
tree dataOutput yang Dihasilkan:
data
├── df.txt
├── paket.txt
└── services.txt
1 directory, 3 filesData server-kantor ini kita bawa sampai akhir proyek: partisi /data yang terisi 89%, empat paket tertunda, dan dua service gagal — bahan yang cukup untuk membuat laporan terasa hidup.
Cek 1: Pemakaian Disk dari Snapshot #
Output df punya enam kolom: filesystem, size, used, avail, persentase, dan mount point. Yang kita butuhkan adalah kolom kelima — persentase — dibandingkan dengan ambang dari konfigurasi. awk cocok untuk pekerjaan ini karena memproses kolom secara alami:
mkdir -p data
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
awk -v ambang=80 'NR > 1 {
pct = $5 + 0
if (pct >= ambang) status = "WASPADA"; else status = "OK"
printf " %-10s %3d%% %s\n", $6, pct, status
}' data/df.txt
echo "== mount yang melewati ambang =="
awk -v ambang=80 'NR > 1 && ($5 + 0) >= ambang { printf "%s%s", pisah, $6; pisah = ", " } END { print "" }' data/df.txtOutput yang Dihasilkan:
/ 73% OK
/data 89% WASPADA
/dev/shm 1% OK
== mount yang melewati ambang ==
/dataBedah baris-per-baris program awk pertama:
-v ambang=80: ambang dikirim sebagai variabel, bukan ditempel ke dalam string program — di skrip final nilainya datang dariDISK_WARN_PCTdi konfigurasi, dan pola-vmembuat kita tidak perlu menyusun ulang program awk-nya.NR > 1: baris pertamadfadalah header (Filesystem Size Used ...), jadi semua aksi hanya berlaku untuk baris data.pct = $5 + 0: penjumlahan dengan nol memaksa awk membaca$5sebagai angka — dan pembacaan angka berhenti di karakter pertama yang bukan digit, sehingga"89%"menjadi89tanpa perlu membuang tanda persen secara terpisah.if (pct >= ambang) status = "WASPADA"; else status = "OK": satu-satunya keputusan cek ini — melewati ambang atau tidak.printf " %-10s %3d%% %s\n", $6, pct, status: mount dirata kiri selebar 10 karakter, angka persentase dirata kanan 3 digit, sehingga kolomnya lurus walau nama mount panjangnya berbeda.
Program kedua menghasilkan daftar mount yang bermasalah dalam satu baris dengan pemisah koma — variabel pisah diisi ", " mulai dari pemakaian kedua, pola yang akan kita pakai lagi untuk daftar paket dan service. Hasilnya juga menjadi kunci rekomendasi di Bagian 3: /data yang muncul di sini adalah satu-satunya mount yang akan disorot laporan.
Cek 2: Paket Update Tertunda #
Simulasi apt-get -s upgrade menghasilkan dua tempat yang memuat daftar paket: rangkuman “The following packages will be upgraded” dan baris-baris Inst. Kita memilih menghitung dari baris Inst karena formatnya paling kaku — selalu diawali Inst lalu nama paket — sehingga paling tahan perubahan format di antara berbagai versi apt. Setiap baris Inst juga membawa nomor versi lama dan baru, yang bisa jadi bahan laporan kelak:
mkdir -p data
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
awk '/^Inst / {
printf " %s\n", $2
n++
} END { printf "total %d paket tertunda\n", n + 0 }' data/paket.txtOutput yang Dihasilkan:
curl
libcurl4
libssl3
openssl
total 4 paket tertundaBedahnya singkat:
/^Inst /: aksi hanya dijalankan untuk baris yang diawali kataInst— pola^menjaga baris seperti “Reading package lists” tidak ikut terhitung, dan spasi setelahInstmembedakannya dari kemungkinan paket bernamaInstrumentasi(yang memang tidak ada, tapi kebiasaan mengunci pola sejak awal murah).$2: kolom kedua barisInstadalah nama paket — versi lama dan baru ada di kolom tiga dan empat.n++ ... END { printf ... n + 0 }: penghitung diinkremen per baris yang cocok;n + 0di blokENDmenjamin angka0tetap tercetak saat tidak ada satu pun paket tertunda, alih-alih string kosong.
Di mesin dengan package manager selain apt, cek ini tinggal diganti sumber snapshot-nya — dnf check-update atau pacman -Qu — sementara bentuk laporannya tidak berubah. Prinsip snapshot membuat pertukaran itu menjadi urusan perintah pengumpul, bukan skrip analisisnya.
Cek 3: Service Gagal #
Snapshot service berformat dua kolom: nama unit dan substate. Memfilter baris yang state-nya failed menjadi satu syarat kolom di awk — ini cek yang paling pendek, tetapi juga yang paling sering menemukan sesuatu di server nyata:
mkdir -p data
cat > data/services.txt <<'EOF'
nginx.service failed
postgresql.service failed
sshd.service running
cron.service running
EOF
awk '$2 == "failed" {
printf " %s\n", $1
n++
} END { printf "total %d service gagal\n", n + 0 }' data/services.txtOutput yang Dihasilkan:
nginx.service
postgresql.service
total 2 service gagalDua hal layak dicatat dari pola $2 == "failed". Pertama, perbandingan string persis lebih aman daripada mencocokkan substring — state activating atau deactivating tidak akan pernah terjaring. Kedua, di komputer sungguhan daftar lengkap state service (running, dead, failed, exited) dan cara membacanya sudah kita bahas di Chapter systemctl
; untuk pemeliharaan, hanya failed yang kita anggap temuan.
Status OK atau WASPADA #
Ketiga cek di atas menghasilkan dua jenis keluaran: baris rinci untuk laporan dan satu angka untuk keputusan. Aturan statusnya kita buat sederhana dan eksplisit — disk WASPADA bila persentase melewati ambang DISK_WARN_PCT dari konfigurasi, paket tertunda WASPADA bila ada satu saja, service gagal WASPADA bila ada satu saja. Sederhana di sini bukan berarti miskin: ambang yang bisa disetel membuat cek disk beradaptasi dengan kebijakan tiap mesin, sementara dua cek lainnya memang tidak punya derajat — paket tertunda atau tidak, service gagal atau tidak.
Ketiga cek kini menghasilkan angka dan daftar yang dibutuhkan. Tetapi pemeriksaan tanpa tindakan hanyalah laporan yang memudar di layar — pada Bagian 2 kita menindaklanjuti temuan dengan pembersihan file yang dilindungi tiga pengaman: whitelist folder, batas umur, dan dry-run sebagai default.
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 ]