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:

PemeriksaanSnapshotPerintah yang menghasilkannyaSinyal WASPADA
Pemakaian diskdata/df.txtdf -hpersentase pemakaian ≥ ambang
Paket tertundadata/paket.txtapt-get -s upgradeada baris Inst
Service gagaldata/services.txtsystemctl list-units --type=serviceada 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:

bash
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.txt

Opsi -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:

bash
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 data

Output yang Dihasilkan:

text
data
├── df.txt
├── paket.txt
└── services.txt

1 directory, 3 files

Data 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:

bash
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.txt

Output yang Dihasilkan:

text
   /           73%  OK
   /data       89%  WASPADA
   /dev/shm     1%  OK
== mount yang melewati ambang ==
/data

Bedah baris-per-baris program awk pertama:

  1. -v ambang=80: ambang dikirim sebagai variabel, bukan ditempel ke dalam string program — di skrip final nilainya datang dari DISK_WARN_PCT di konfigurasi, dan pola -v membuat kita tidak perlu menyusun ulang program awk-nya.
  2. NR > 1: baris pertama df adalah header (Filesystem Size Used ...), jadi semua aksi hanya berlaku untuk baris data.
  3. pct = $5 + 0: penjumlahan dengan nol memaksa awk membaca $5 sebagai angka — dan pembacaan angka berhenti di karakter pertama yang bukan digit, sehingga "89%" menjadi 89 tanpa perlu membuang tanda persen secara terpisah.
  4. if (pct >= ambang) status = "WASPADA"; else status = "OK": satu-satunya keputusan cek ini — melewati ambang atau tidak.
  5. 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:

bash
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.txt

Output yang Dihasilkan:

text
   curl
   libcurl4
   libssl3
   openssl
total 4 paket tertunda

Bedahnya singkat:

  1. /^Inst /: aksi hanya dijalankan untuk baris yang diawali kata Inst — pola ^ menjaga baris seperti “Reading package lists” tidak ikut terhitung, dan spasi setelah Inst membedakannya dari kemungkinan paket bernama Instrumentasi (yang memang tidak ada, tapi kebiasaan mengunci pola sejak awal murah).
  2. $2: kolom kedua baris Inst adalah nama paket — versi lama dan baru ada di kolom tiga dan empat.
  3. n++ ... END { printf ... n + 0 }: penghitung diinkremen per baris yang cocok; n + 0 di blok END menjamin angka 0 tetap 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:

bash
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.txt

Output yang Dihasilkan:

text
   nginx.service
   postgresql.service
total 2 service gagal

Dua 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
bash
#!/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
bash
#!/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 ]