Foto ulang tahun tersebar di ponsel setiap anggota keluarga, dokumen penting tersimpan di laptop yang dipakai bergantian, dan satu-satunya salinan resep warisan ibu ada di harddisk eksternal yang bunyinya sudah tidak wajar. Data keluarga biasanya hidup begitu saja — tersebar, tanpa pemilik yang jelas, dan sering tanpa satu pun salinan cadangan. Proyek ini mengubah satu perangkat hemat di rumah menjadi home server yang menjawab tiga kebutuhan itu sekaligus: berbagi file, backup otomatis yang terjadwal, dan laporan status yang bisa dibaca kapan saja. Seluruh layanan dibangun sebagai unit systemd yang dikelola rapi — dijalankan oleh user khusus non-root, dijadwalkan lewat timer, dan diperiksa kesehatannya lewat skrip — bukan proses yang ditempel di background lalu dilupakan.

Perangkat yang kita pakai sengaja yang paling murah dan paling sering terlupakan: mini PC bekas kantor, laptop tua yang layarnya rusak, atau Raspberry Pi yang menganggur di laci. Ketiganya justru cocok karena tugasnya sederhana — bagaimana memilih dan menghidupkan perangkat semacam ini sudah kita bahas di section Linux untuk Perangkat Lama .

Layanan yang kita bangun dalam proyek ini:

  • berbagi file keluarga — folder /srv/berbagi yang bisa dijangkau semua perangkat di jaringan rumah, dilayani oleh unit berbagi-file.service yang menjalankan python3 -m http.server pada port 8765,
  • backup harian terjadwal — unit backup-keluarga.timer yang memicu backup-keluarga.service setiap hari jam 02:30, menjalankan pola rsync+tar dari proyek Skrip Backup Otomatis ,
  • dashboard status — skrip status-home-server.sh yang memeriksa tiga hal (layanan jalan, disk data, umur backup terakhir) dari snapshot beku, lalu melaporkannya dengan kode keluar yang bisa dibaca mesin,
  • peta layanan nyata — Syncthing, Jellyfin, dan pi-hole dipetakan fungsinya supaya kita tahu kapan waktunya naik kelas dari layanan mini ke layanan sungguhan.

Tampilan Program Akhir #

Sesi berikut menunjukkan hasil akhir proyek pada data contoh dengan tanggal acuan dikunci ke 2026-02-15. Perintah pertama menjalankan skrip status dari folder kerja yang berisi home-server.conf dan snapshot di data/; perintah kedua memvalidasi ketiga unit file sekaligus — systemd-analyze verify tidak mencetak apa pun saat semuanya valid:

console
$ HARI_INI=2026-02-15 bash deploy/status-home-server.sh
================================================================
STATUS HOME SERVER
Tanggal acuan : 2026-02-15
================================================================
layanan berbagi : JALAN (port 8765)
disk data       : /srv/data 72% (ambang 85%)
backup terakhir : backup-20260214 (umur 1 hari, maksimal 2)
----------------------------------------------------------------
temuan          : tidak ada — server sehat
$ systemd-analyze verify config/berbagi-file.service config/backup-keluarga.service config/backup-keluarga.timer
$ echo $?
0

Laporan itu ringkas karena memang ditujukan untuk dibaca sekilas: tiga baris status dan satu baris kesimpulan. Di baliknya ada keputusan desain yang menjadi inti proyek — snapshot dibekukan supaya hasil bisa diulang, setiap temuan menyala sebagai bit pada exit code, dan setiap unit divalidasi strukturnya sebelum dipasang.

Arsitektur Proyek #

Seluruh keputusan di atas bertemu dalam satu gambar berikut:

text
   Perangkat keluarga (laptop, ponsel, TV)
        │ salin / ambil file
        ▼
┌──────────────────────────────────────────────┐
│ HOME SERVER — mini PC / laptop tua          │
│                                              │
│ /srv/berbagi       folder file keluarga     │
│ /srv/foto-keluarga foto dari semua ponsel   │
│ /srv/data           disk data (fstab nofail)│
│ /srv/backup         tujuan backup harian    │
│                                              │
│ berbagi-file.service                         │
│   └─ python3 -m http.server 8765             │
│      (user berbagi, hardening unit)          │
│                                              │
│ backup-keluarga.timer ── tiap hari 02:30 ──┐ │
│   └─ backup-keluarga.service (oneshot)     │ │
│        └─ rsync + tar (Proyek Skrip Backup)│ │
│                                             │ │
│ status-home-server.sh ─ laporan + log temuan│ │
└──────────────────────────────────────────────┘

Empat folder di bawah /srv dipisah perannya: data hidup di berbagi dan foto-keluarga, disk data besar termuat di data, dan hasil backup menumpuk di backup. Tiga unit systemd mengurus hidup matinya layanan, dan skrip status menutupnya dengan laporan — semuanya akan kita bangun dan uji satu per satu.

Prasyarat #

Proyek ini merangkai kembali materi dari beberapa section. Chapter yang paling sering dirujuk:

Tujuan Pembelajaran #

Setelah menyelesaikan proyek ini, kita mampu:

  • menyiapkan fondasi server yang layak huni: OS tanpa GUI, IP stabil lewat DHCP reservation, SSH, disk data terpisah dengan nofail, dan struktur /srv yang jelas perannya,
  • menjelaskan mengapa layanan berjalan sebagai user sistem non-root dan menyiapkan folder dengan permission yang sesuai,
  • menulis unit service dan timer systemd yang lolos systemd-analyze verify sebelum dipasang,
  • membuktikan sebuah layanan benar-benar bekerja lewat curl dan ss — bukan sekadar percaya statusnya,
  • menerapkan hardening unit (User=, ProtectSystem=, PrivateTmp=) dan membedah artinya,
  • membangun laporan status deterministik dari snapshot beku dengan alert berupa log dan exit code ber-bitmask,
  • membuktikan seluruh klaim lewat test suite bash yang menjalankan layanan sungguhan.

Tahapan Pengerjaan #

Proyek dikerjakan dalam tiga bagian berurutan, dan tiap bagian diakhiri dengan keadaan yang bisa diperiksa:

  1. Bagian 1: Persiapan Home Server — peran server, OS tanpa GUI, IP stabil, SSH, disk data dengan fstab nofail, struktur /srv, user layanan, dan skrip cek kesiapan.
  2. Bagian 2: Menjalankan Layanan — unit berbagi-file.service dan pasangan backup-keluarga.service/.timer ditulis dan divalidasi, layanan mini dijalankan dan dibuktikan, lalu di-hardening.
  3. Bagian 3: Monitoring dan Backup — snapshot beku, skrip status dengan laporan dan kode keluar ber-bitmask, alert sederhana, serta test suite yang mengunci semuanya.

Mulailah dari Bagian 1 karena setiap keputusan di sana — struktur folder, user layanan, mount point — dipakai terus oleh unit dan skrip di dua bagian berikutnya. Berkas proyek (config/ berisi tiga unit systemd dan home-server.conf, deploy/ berisi cek-kesiapan.sh dan status-home-server.sh, tests/run-tests.sh, serta docs/README.txt) tersedia pada bagian Berkas Sumber di bawah halaman setiap bagian, sehingga hasil pekerjaan bisa dibandingkan dengan versi referensi kapan saja.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

deploy/cek-kesiapan.sh

Unduh berkas
bash
#!/usr/bin/env bash
# cek-kesiapan.sh — periksa kesiapan struktur home server.
#
# Memeriksa folder layanan di bawah SRV_ROOT, permission dan
# kepemilikan folder berbagi, serta entri fstab untuk disk data.
# Semua target bisa digeser lewat variabel lingkungan sehingga
# pemeriksaan bisa dilatih pada fixture:
#   SRV_ROOT     akar struktur layanan (default: /srv)
#   FSTAB_FILE   berkas fstab yang diperiksa (default: /etc/fstab)
#   LAYANAN_USER user pemilik folder berbagi (default: user saat ini)
#   DATA_MOUNT   mount point disk data (default: /srv/data)
#
# Kode keluar: 0 = siap dijalankan; selain itu = jumlah temuan.

set -u

SRV_ROOT="${SRV_ROOT:-/srv}"
FSTAB_FILE="${FSTAB_FILE:-/etc/fstab}"
LAYANAN_USER="${LAYANAN_USER:-$(id -un)}"
DATA_MOUNT="${DATA_MOUNT:-/srv/data}"

temuan=0

lolos() {
    printf 'OK    %s\n' "$1"
}

gagal() {
    printf 'GAGAL %s\n' "$1"
    temuan=$((temuan + 1))
}

# --- folder berbagi: ada, mode 750, dimiliki user layanan --------------
if [ -d "$SRV_ROOT/berbagi" ]; then
    mode=$(stat -c %a "$SRV_ROOT/berbagi")
    if [ "$mode" = "750" ]; then
        lolos "folder $SRV_ROOT/berbagi (mode $mode)"
    else
        gagal "folder $SRV_ROOT/berbagi mode $mode, harapan 750"
    fi
    pemilik=$(stat -c %U "$SRV_ROOT/berbagi")
    if [ "$pemilik" = "$LAYANAN_USER" ]; then
        lolos "pemilik $SRV_ROOT/berbagi sesuai user layanan"
    else
        gagal "pemilik $SRV_ROOT/berbagi bukan user layanan"
    fi
else
    gagal "folder $SRV_ROOT/berbagi tidak ditemukan"
    # permission dan kepemilikan tidak bisa diperiksa bila foldernya
    # pun tidak ada; dua temuan cukup mewakili satu folder hilang.
fi

# --- folder data keluarga -------------------------------------------------
for d in foto-keluarga backup; do
    if [ -d "$SRV_ROOT/$d" ]; then
        lolos "folder $SRV_ROOT/$d"
    else
        gagal "folder $SRV_ROOT/$d tidak ditemukan"
    fi
done

# --- entri fstab disk data: nofail wajib ----------------------------------
# $2 mount point, $4 opsi; nofail dicocokkan sebagai satu opsi utuh
# sehingga opsi seperti "mynofailx" tidak lolos mengaku nofail.
if awk -v mnt="$DATA_MOUNT" '
        $0 !~ /^[[:space:]]*#/ && $2 == mnt &&
        $4 ~ /(^|,)nofail(,|$)/ { found = 1 }
        END { exit found ? 0 : 1 }' "$FSTAB_FILE" 2> /dev/null; then
    lolos "entri fstab untuk $DATA_MOUNT memuat nofail"
else
    gagal "fstab belum memuat entri nofail untuk $DATA_MOUNT"
fi

# --- ringkasan ------------------------------------------------------------
if [ "$temuan" -eq 0 ]; then
    printf 'server siap dijalankan: 0 temuan\n'
else
    printf 'server belum siap: %d temuan\n' "$temuan"
fi
exit "$temuan"

deploy/status-home-server.sh

Unduh berkas
bash
#!/usr/bin/env bash
# status-home-server.sh — dashboard status home server.
#
# Membaca snapshot beku dari folder data/ (ss.txt: hasil `ss -tln`,
# df.txt: hasil `df -h`) dan daftar run backup (backup-YYYYMMDD) di
# folder BACKUP_DIR, lalu menulis laporan status ke stdout sekaligus
# baris temuan ke berkas log. Pola snapshot membuat hasilnya bisa
# diulang dan diuji — pola yang sama dipakai Proyek Otomasi
# Pemeliharaan Sistem.
#
# Variabel lingkungan (berguna untuk pengujian):
#   CONF      berkas konfigurasi yang dibaca (default: home-server.conf)
#   DATA_DIR  folder snapshot (default: data)
#   HARI_INI  tanggal acuan umur backup (default: hari ini, YYYY-MM-DD)
#
# Kode keluar (temuan bisa gabungan — jumlahkan nilainya):
#   0 sehat                        1 disk data melewati ambang
#   2 layanan berbagi tidak jalan  4 backup terlalu tua / belum ada

set -u

CONF="${CONF:-home-server.conf}"
DATA_DIR="${DATA_DIR:-data}"
HARI_INI="${HARI_INI:-$(date +%F)}"

if [ -f "$CONF" ]; then
    # shellcheck source=/dev/null
    . "$CONF"
fi

PORT_BERBAGI="${PORT_BERBAGI:-8765}"
DATA_MOUNT="${DATA_MOUNT:-/srv/data}"
BACKUP_DIR="${BACKUP_DIR:-/srv/backup/run}"
BACKUP_MAX_UMUR_HARI="${BACKUP_MAX_UMUR_HARI:-2}"
DISK_AMBANG_PCT="${DISK_AMBANG_PCT:-85}"
LOG_FILE="${LOG_FILE:-status-home-server.log}"

GARIS='----------------------------------------------------------------'

# --- 1. Layanan berbagi: cari baris LISTEN pada port dari snapshot ----
# Kolom ke-4 output ss adalah "alamat:port", jadi port dicocokkan
# sebagai akhiran kolom — bentuk [::]:8765 pun lolos pola yang sama.
layanan="MATI"
if awk -v port="$PORT_BERBAGI" '
        $1 == "LISTEN" && $4 ~ ":" port "$" { ketemu = 1 }
        END { exit ketemu ? 0 : 1 }' "$DATA_DIR/ss.txt" 2> /dev/null; then
    layanan="JALAN"
fi

# --- 2. Disk data: persentase pemakaian dari snapshot df --------------
# Kolom ke-5 df adalah Use% ("+0" membuang tanda %), kolom ke-6
# mount point. Mount yang tidak ada berarti disk data gagal termuat.
disk_pct=$(awk -v mnt="$DATA_MOUNT" '
    $6 == mnt { print $5 + 0; exit }' "$DATA_DIR/df.txt" 2> /dev/null)

# --- 3. Backup terakhir: umur dihitung dari nama folder run -----------
backup_terakhir=$(ls -1 "$BACKUP_DIR" 2> /dev/null |
    grep -E '^backup-[0-9]{8}$' | sort | tail -n 1)

umur=""
if [ -n "$backup_terakhir" ]; then
    tgl="${backup_terakhir#backup-}"
    tgl_iso="${tgl:0:4}-${tgl:4:2}-${tgl:6:2}"
    umur=$(( ( $(date -d "$HARI_INI" +%s) -
               $(date -d "$tgl_iso" +%s) ) / 86400 ))
fi

# --- 4. Kumpulkan temuan ------------------------------------------------
temuan=0
n_temuan=0
daftar_temuan=""

catat() {
    n_temuan=$((n_temuan + 1))
    daftar_temuan="$daftar_temuan
- $1"
}

if [ "$layanan" = "MATI" ]; then
    temuan=$((temuan + 2))
    catat "layanan berbagi tidak jalan (port $PORT_BERBAGI tidak mendengarkan)"
fi

if [ -z "$disk_pct" ]; then
    temuan=$((temuan + 1))
    catat "disk data $DATA_MOUNT tidak termuat — periksa fstab"
elif [ "$disk_pct" -ge "$DISK_AMBANG_PCT" ]; then
    temuan=$((temuan + 1))
    catat "disk data melewati ambang: ${disk_pct}% dari ${DISK_AMBANG_PCT}%"
fi

if [ -z "$backup_terakhir" ]; then
    temuan=$((temuan + 4))
    catat "belum ada run backup di $BACKUP_DIR"
elif [ "$umur" -gt "$BACKUP_MAX_UMUR_HARI" ]; then
    temuan=$((temuan + 4))
    catat "backup terlalu tua: $umur hari (maksimal $BACKUP_MAX_UMUR_HARI)"
fi

# --- 5. Tulis laporan ke stdout dan temuan ke log -----------------------
printf '================================================================\n'
printf 'STATUS HOME SERVER\n'
printf 'Tanggal acuan : %s\n' "$HARI_INI"
printf '================================================================\n'
if [ "$layanan" = "JALAN" ]; then
    printf 'layanan berbagi : JALAN (port %s)\n' "$PORT_BERBAGI"
else
    printf 'layanan berbagi : MATI (port %s tidak mendengarkan)\n' "$PORT_BERBAGI"
fi
if [ -n "$disk_pct" ]; then
    if [ "$disk_pct" -ge "$DISK_AMBANG_PCT" ]; then
        printf 'disk data       : %s %s%% MELEWATI AMBANG %s%%\n' \
            "$DATA_MOUNT" "$disk_pct" "$DISK_AMBANG_PCT"
    else
        printf 'disk data       : %s %s%% (ambang %s%%)\n' \
            "$DATA_MOUNT" "$disk_pct" "$DISK_AMBANG_PCT"
    fi
else
    printf 'disk data       : %s tidak ada di snapshot df\n' "$DATA_MOUNT"
fi
if [ -n "$backup_terakhir" ]; then
    printf 'backup terakhir : %s (umur %s hari, maksimal %s)\n' \
        "$backup_terakhir" "$umur" "$BACKUP_MAX_UMUR_HARI"
else
    printf 'backup terakhir : belum ada run backup\n'
fi
printf '%s\n' "$GARIS"

if [ "$temuan" -eq 0 ]; then
    printf 'temuan          : tidak ada — server sehat\n'
else
    printf 'temuan          : %d\n' "$n_temuan"
    # ${var#$'\n'} membuang baris kosong pertama dari daftar.
    printf '%s\n' "${daftar_temuan#$'\n'}"
    # Alert sederhana: satu baris per temuan ke berkas log.
    mkdir -p "$(dirname "$LOG_FILE")"
    printf '%s\n' "${daftar_temuan#$'\n'}" |
        while IFS= read -r baris; do
            printf '[%s] TEMUAN: %s\n' "$HARI_INI" "${baris#- }" >> "$LOG_FILE"
        done
    printf 'temuan dicatat ke: %s\n' "$LOG_FILE"
fi

exit "$temuan"

tests/run-tests.sh

Unduh berkas
bash
#!/usr/bin/env bash
# run-tests.sh — test suite untuk paket layanan home server.
#
# Suite menyiapkan folder kerja terisolasi (mktemp), menyalin unit
# systemd dari config/ dan kedua skrip dari deploy/, lalu menjalankan:
#   1. validasi unit lewat systemd-analyze verify,
#   2. pemeriksaan directive penting lewat grep,
#   3. layanan mini yang benar-benar mendengarkan dan menjawab HTTP,
#   4. skrip kesiapan pada fixture siap dan rusak,
#   5. skrip status pada fixture sehat dan kombinasi temuan.
# 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/config/berbagi-file.service" \
   "$PROJ_DIR/config/backup-keluarga.service" \
   "$PROJ_DIR/config/backup-keluarga.timer" \
   "$PROJ_DIR/config/home-server.conf" \
   "$PROJ_DIR/deploy/status-home-server.sh" \
   "$PROJ_DIR/deploy/cek-kesiapan.sh" "$WORK/"
cd "$WORK" || exit 1

server_pid=""
cleanup() {
    if [ -n "$server_pid" ]; then
        kill "$server_pid" 2> /dev/null
        wait "$server_pid" 2> /dev/null
    fi
}
trap cleanup EXIT

# --- uji 1: unit valid menurut systemd-analyze verify -------------------
systemd-analyze verify berbagi-file.service
assert_eq 0 "$?" "berbagi-file.service lolos systemd-analyze verify"
systemd-analyze verify backup-keluarga.service backup-keluarga.timer
assert_eq 0 "$?" "unit backup lolos systemd-analyze verify"

# --- uji 2: directive penting ada di tempatnya ----------------------------
grep -q '^User=berbagi$' berbagi-file.service
assert_eq 0 "$?" "unit berbagi berjalan sebagai user layanan non-root"
grep -q '^WorkingDirectory=/srv/berbagi$' berbagi-file.service
assert_eq 0 "$?" "WorkingDirectory mengarah ke folder berbagi"
grep -q '^ExecStart=/usr/bin/python3 -m http.server 8765' \
    berbagi-file.service
assert_eq 0 "$?" "ExecStart menjalankan layanan pada port 8765"
grep -q '^ProtectSystem=' berbagi-file.service
grep -q '^PrivateTmp=true$' berbagi-file.service
assert_eq 0 "$?" "hardening ProtectSystem dan PrivateTmp aktif"
grep -q '^OnCalendar=' backup-keluarga.timer
assert_eq 0 "$?" "timer memuat OnCalendar"
grep -q '^Persistent=true$' backup-keluarga.timer
assert_eq 0 "$?" "timer Persistent mengejar jadwal yang terlewat"

# --- uji 3: layanan mini benar-benar menjawab ------------------------------
mkdir -p srv/berbagi
printf 'jadwal piknik keluarga\n' > srv/berbagi/jadwal-piknik.txt
python3 -m http.server 8765 --directory srv/berbagi >/dev/null 2>&1 &
server_pid=$!
siap=1
for i in 1 2 3 4 5 6 7 8 9 10; do
    curl -s -o /dev/null http://127.0.0.1:8765/ && { siap=0; break; }
    sleep 0.3
done
assert_eq 0 "$siap" "layanan mini siap menerima permintaan"
kode=$(curl -s -o /dev/null -w '%{http_code}' \
    http://127.0.0.1:8765/jadwal-piknik.txt)
assert_eq 200 "$kode" "berkas dijawab HTTP 200"
isi=$(curl -s http://127.0.0.1:8765/jadwal-piknik.txt)
assert_eq "jadwal piknik keluarga" "$isi" "isi berkas utuh sampai klien"
ss -tln | awk -v port="8765" '$1 == "LISTEN" && $4 ~ ":" port "$"' \
    | grep -q .
assert_eq 0 "$?" "ss melihat port 8765 dalam keadaan LISTEN"

# --- uji 4: skrip kesiapan pada fixture -------------------------------------
mkdir -p srv/foto-keluarga srv/backup srv/backup/run
chmod 750 srv/berbagi
printf 'UUID=5c1b2a3d-1111-4222-8333-444455556666 /srv/data ext4 defaults,nofail 0 2\n' > fstab.data
SRV_ROOT=srv FSTAB_FILE=fstab.data bash cek-kesiapan.sh > cek-siap.txt 2>&1
assert_eq 0 "$?" "layout siap lolos pemeriksaan"
grep -q 'server siap dijalankan: 0 temuan' cek-siap.txt
assert_eq 0 "$?" "laporan kesiapan menyatakan siap"
chmod 755 srv/berbagi
sed 's/,nofail//' fstab.data > fstab.rusak
SRV_ROOT=srv FSTAB_FILE=fstab.rusak bash cek-kesiapan.sh > /dev/null 2>&1
assert_eq 2 "$?" "mode salah dan nofail hilang terdeteksi (exit 2)"
chmod 750 srv/berbagi

# --- uji 5: skrip status pada fixture sehat ---------------------------------
# Snapshot ss diambil saat layanan masih jalan (uji 3), lalu layanan
# dihentikan supaya suite tidak meninggalkan proses.
mkdir -p data
ss -tln > data/ss.txt
kill "$server_pid"
wait "$server_pid" 2> /dev/null
server_pid=""
ss -tln | awk -v port="8765" '$1 == "LISTEN" && $4 ~ ":" port "$"' \
    | wc -l | grep -qx 0
assert_eq 0 "$?" "port 8765 bebas setelah layanan berhenti"

cat > data/df.txt <<'EOF'
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1         50G   20G   28G  41% /
/dev/sdb1       200G  143G   47G  72% /srv/data
EOF
mkdir -p srv/backup/run/backup-20260214
HARI_INI=2026-02-15 bash status-home-server.sh > laporan-sehat.txt
assert_eq 0 "$?" "fixture sehat keluar 0"
grep -q '^layanan berbagi : JALAN' laporan-sehat.txt
assert_eq 0 "$?" "laporan menyatakan layanan JALAN"
grep -q '72% (ambang 85%)' laporan-sehat.txt
assert_eq 0 "$?" "disk data terbaca 72% dari snapshot"
grep -q 'backup-20260214 (umur 1 hari, maksimal 2)' laporan-sehat.txt
assert_eq 0 "$?" "umur backup terhitung dari tanggal acuan"

# --- uji 6: setiap temuan menyalakan bit kode keluar sendiri -----------------
# Tanggal acuan 2026-02-15: backup-20260214 berumur 1 hari (segar),
# backup-20260120 berumur 26 hari (terlalu tua).
mv data/ss.txt data/ss-jalan.txt
ss -tln | grep -v ':8765 ' > data/ss.txt || true
HARI_INI=2026-02-15 bash status-home-server.sh > /dev/null 2>&1
assert_eq 2 "$?" "layanan mati menyalakan bit 2"
mv data/ss-jalan.txt data/ss.txt

sed 's/ 72% / 91% /' data/df.txt > data/df-91.txt
mv data/df-91.txt data/df.txt
HARI_INI=2026-02-15 bash status-home-server.sh > /dev/null 2>&1
assert_eq 1 "$?" "disk 91% melewati ambang menyalakan bit 1"

rm -rf srv/backup/run/backup-20260214
mkdir -p srv/backup/run/backup-20260120
HARI_INI=2026-02-15 bash status-home-server.sh > /dev/null 2>&1
assert_eq 5 "$?" "disk 91% dan backup tua menyalakan bit 1+4"

ss -tln | grep -v ':8765 ' > data/ss.txt || true
HARI_INI=2026-02-15 bash status-home-server.sh > laporan-temuan.txt 2>&1
assert_eq 7 "$?" "tiga temuan menyalakan bit 1+2+4"
grep -Eq '^temuan[[:space:]]+: 3$' laporan-temuan.txt
assert_eq 0 "$?" "laporan menyebut tiga temuan"
grep -q 'backup terlalu tua: 26 hari' laporan-temuan.txt
assert_eq 0 "$?" "umur backup tua dihitung benar"
grep -q 'TEMUAN: layanan berbagi tidak jalan' \
    srv/backup/status-home-server.log
assert_eq 0 "$?" "temuan tercatat ke berkas log"

# --- 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 ]