Server yang dinyalakan lalu dilupakan adalah server yang kegagalannya juga diam-diam: layanan bisa mati berhari-hari tanpa ada yang sadar, disk mengisi pelan-pelan sampai backup tidak lagi muat, dan timer yang gagal sejak minggu lalu terus tercatat “terjadwal”. Bagian ini memasang pengawasnya. Kita membangun skrip status yang membaca snapshot beku — pola yang sudah kita pakai di proyek Otomasi Pemeliharaan Sistem — untuk menjawab tiga pertanyaan berharga: apakah layanan jalan, berapa penuh disk data, dan kapan backup terakhir yang berhasil. Temuannya dilaporkan ke layar, dicatat ke log, dan diberi kode keluar yang bisa dibaca mesin — lalu seluruh janji itu dikunci oleh test suite.
Pola Snapshot untuk Status #
Skrip status tidak menjalankan ss atau df langsung, melainkan membaca snapshot yang dibekukan di folder data/. Alasannya sama dengan proyek pemeliharaan: hasil bisa diulang, bisa diaudit, dan bisa diuji — laporan hari ini tidak berubah walau dibaca sepuluh kali. Di server sungguhan, dua snapshot itu diambil dengan dua perintah sebelum skrip dijalankan: ss -tln > data/ss.txt dan df -h > data/df.txt. Blok berikut mengerjakan persis itu — menyalakan layanan mini dari Bagian 2, membekukan ss.txt saat ia mendengarkan, lalu menghentikannya lagi; df.txt kita tulis sebagai data contoh karena angka disk aslinya berbeda di tiap mesin:
mkdir -p latihan/data latihan/srv/berbagi
printf 'jadwal piknik keluarga\n' > latihan/srv/berbagi/jadwal-piknik.txt
python3 -m http.server 8765 --directory latihan/srv/berbagi >/dev/null 2>&1 &
server_pid=$!
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/ && break
sleep 0.3
done
ss -tln > latihan/data/ss.txt
kill "$server_pid"
wait "$server_pid" 2>/dev/null
echo "== isi snapshot ss.txt (hanya baris port 8765) =="
awk 'NR == 1 || /:8765 /' latihan/data/ss.txt
cat > latihan/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 latihan/srv/backup/run/backup-20260214
printf 'arsip foto keluarga\n' > latihan/srv/backup/run/backup-20260214/foto-keluarga.tar.gz
tree latihanOutput yang Dihasilkan:
== isi snapshot ss.txt (hanya baris port 8765) ==
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 5 0.0.0.0:8765 0.0.0.0:*
latihan
├── data
│ ├── df.txt
│ └── ss.txt
└── srv
├── backup
│ └── run
│ └── backup-20260214
│ └── foto-keluarga.tar.gz
└── berbagi
└── jadwal-piknik.txt
7 directories, 4 filesDua hal dari blok itu: baris LISTEN pada 0.0.0.0:8765 adalah bukti yang dibaca skrip status nanti (snapshot diambil saat layanan masih hidup, sebelum kill), dan folder backup-20260214 sengaja dinamai bertanggal — nama itulah yang membuat umur backup bisa dihitung tanpa menyentuh waktu modifikasi.
Skrip status-home-server.sh #
Skrip kita membaca konfigurasi dari berkas terpisah (home-server.conf — pola yang sama dengan seluruh proyek sebelumnya), memeriksa tiga hal dari snapshot, lalu mencetak laporan empat baris. Karena tiap blok latihan mandiri, blok berikut menuliskan ulang snapshot yang dibekukan tadi persis seperti hasilnya, lalu menjalankan skrip pada dua skenario: sehat, dan ketiga-ketiganya bermasalah sekaligus. Versi lengkapnya tersedia sebagai deploy/status-home-server.sh pada bagian Berkas Sumber di bawah halaman ini:
mkdir -p latihan/data latihan/srv/backup/run
cat > latihan/data/ss.txt <<'EOF'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 5 0.0.0.0:8765 0.0.0.0:*
EOF
cat > latihan/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 latihan/srv/backup/run/backup-20260214
cat > latihan/home-server.conf <<'EOF'
PORT_BERBAGI=8765
DATA_MOUNT=/srv/data
BACKUP_DIR=latihan/srv/backup/run
BACKUP_MAX_UMUR_HARI=2
DISK_AMBANG_PCT=85
LOG_FILE=latihan/status-home-server.log
EOF
cat > latihan/status-home-server.sh <<'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"
BASH
echo "== skenario sehat =="
CONF=latihan/home-server.conf DATA_DIR=latihan/data HARI_INI=2026-02-15 \
bash latihan/status-home-server.sh
echo "exit=$?"
echo "== skenario tiga temuan =="
sed -i '/8765/d' latihan/data/ss.txt
cat > latihan/data/df.txt <<'EOF'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 20G 28G 41% /
/dev/sdb1 200G 178G 12G 91% /srv/data
EOF
rm -rf latihan/srv/backup/run/backup-20260214
mkdir -p latihan/srv/backup/run/backup-20260120
CONF=latihan/home-server.conf DATA_DIR=latihan/data HARI_INI=2026-02-15 \
bash latihan/status-home-server.sh
echo "exit=$?"
echo "== log temuan =="
cat latihan/status-home-server.logOutput yang Dihasilkan:
== skenario sehat ==
================================================================
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
exit=0
== skenario tiga temuan ==
================================================================
STATUS HOME SERVER
Tanggal acuan : 2026-02-15
================================================================
layanan berbagi : MATI (port 8765 tidak mendengarkan)
disk data : /srv/data 91% MELEWATI AMBANG 85%
backup terakhir : backup-20260120 (umur 26 hari, maksimal 2)
----------------------------------------------------------------
temuan : 3
- layanan berbagi tidak jalan (port 8765 tidak mendengarkan)
- disk data melewati ambang: 91% dari 85%
- backup terlalu tua: 26 hari (maksimal 2)
temuan dicatat ke: latihan/status-home-server.log
exit=7
== log temuan ==
[2026-02-15] TEMUAN: layanan berbagi tidak jalan (port 8765 tidak mendengarkan)
[2026-02-15] TEMUAN: disk data melewati ambang: 91% dari 85%
[2026-02-15] TEMUAN: backup terlalu tua: 26 hari (maksimal 2)Bagian-bagian kuncinya:
awk ... $4 ~ ":" port "$": kolom ke-4 outputssberbentukalamat:port, jadi port dicocokkan sebagai akhiran kolom —0.0.0.0:8765dan[::]:8765sama-sama lolos. Snapshot tanpa baris itu berarti tidak ada yang mendengarkan: layanan mati.$6 == mnt { print $5 + 0 }: untuk disk, kolom ke-6dfadalah mount point dan kolom ke-5 persentasenya;+ 0membuang tanda%. Bila mount/srv/datatidak ada sama sekali di snapshot, itu temuan tersendiri — kemungkinan besar disk data gagal termuat danfstabperlu diperiksa.- Umur backup dari nama folder:
backup-20260214dipilih paling baru lewatsort | tail -n 1(nama bertanggal urut wajar), lalu tanggalnya dipotong dari nama dan selisihnya dihitung terhadapHARI_INIdengan aritmetika epoch —$(( ( ... ) / 86400 ))menghasilkan selisih hari. MengunciHARI_INIsaat menguji membuat umur deterministik; format tanggal yang diterimadate -dterdokumentasi di date(1) . catat()dan bit temuan: setiap temuan menambahkan satu baris ke daftar sekaligus menyalakan satu bit pada variabeltemuan. Pada skenario kedua, tiga temuan menumpuk menjadi2+1+4 = 7.- Laporan dan log: status per pemeriksaan dicetak apa adanya (JALAN/MATI, persentase, umur), temuan dirangkum di bawah garis, dan setiap temuan juga menempel satu baris
[tanggal] TEMUAN: ...ke berkas log — jejak yang bisa dibaca mundur bila kita ingin tahu sejak kapan masalah itu hidup.
Temuan sebagai Kode Keluar #
Bagian paling penting dari laporan justru yang tidak tercetak: exit code. Skrip ini memakai bitmask — setiap jenis temuan menyala satu bit, dan nilainya dijumlahkan:
| Kode | Arti | Penyebab tipikal |
|---|---|---|
| 0 | sehat | — |
| 1 | disk data bermasalah | melewati ambang, atau tidak termuat |
| 2 | layanan berbagi mati | unit berhenti, port tidak didengarkan |
| 3 | kombinasi 1+2 | disk dan layanan sekaligus |
| 4 | backup bermasalah | terlalu tua, atau belum pernah jalan |
| 5, 6, 7 | kombinasi lainnya | jumlahkan bitnya |
Kode gabungan inilah alasan memilih bitmask: satu angka sudah menceritakan kombinasi temuan apa saja yang hidup — 7 berarti ketiganya, 5 berarti disk dan backup tanpa masalah layanan. Pemakaian praktisnya sederhana:
bash deploy/status-home-server.sh || echo "ada temuan, kode $?"
if ! bash deploy/status-home-server.sh; then
echo "server bermasalah — periksa laporan di atas"
fiDijadwalkan lewat cron, output skrip otomatis dikirim email ke pemilik crontab bila exit code-nya bukan nol — alert paling sederhana yang bisa dibangun tanpa layanan eksternal. Pola penjadwalan dan opsi mail cron dibahas di Scheduling Scripts .
Backup dan Pemeliharaan Berkala #
Mesin backup di balik backup-keluarga.service adalah skrip dari proyek Skrip Backup Otomatis
: mirror rsync untuk kondisi terkini, arsip tar.gz per run bertanggal, checksum SHA-256, dan rotasi retensi. Timer dari Bagian 2 hanya mengganti “ingat menjalankan backup” menjadi “sistem yang menjalankannya” — skripnya sendiri sudah kita buktikan di proyek itu, termasuk simulasi bencana dan test restore-nya. Konfigurasi yang perlu disesuaikan di server: BACKUP_DIRS memuat /srv/foto-keluarga dan /srv/berbagi, sementara BACKUP_DEST mengarah ke disk data (/srv/backup).
Selain backup, server juga butuh dirawat: cache dan log lama dibersihkan, paket update tertunda ditinjau, dan service gagal ditindaklanjuti. Ketiganya adalah pekerjaan yang sudah dibangun tuntas di proyek Otomasi Pemeliharaan Sistem — cukup pasang skripnya di server yang sama, dan home server kita jadi merawat dirinya sendiri secara berkala.
Test Suite #
Terakhir, seluruh klaim proyek dikunci oleh test suite. Suite menyiapkan folder kerja terisolasi, menyalin unit dan skrip dari paket proyek, lalu menguji empat lapis: validasi unit lewat systemd-analyze verify, directive penting lewat grep, layanan mini yang benar-benar mendengarkan dan menjawab, serta kedua skrip pada fixture. Versi lengkapnya tersedia sebagai tests/run-tests.sh pada bagian Berkas Sumber di bawah halaman ini:
mkdir -p proyek/config proyek/deploy proyek/tests
cat > proyek/config/berbagi-file.service <<'EOF'
# berbagi-file.service — layanan berbagi file keluarga.
# Dipasang sebagai /etc/systemd/system/berbagi-file.service
# pada server; aktifkan dengan: systemctl enable --now berbagi-file
[Unit]
Description=Berbagi file keluarga via HTTP sederhana
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=berbagi
Group=berbagi
WorkingDirectory=/srv/berbagi
ExecStart=/usr/bin/python3 -m http.server 8765 --bind 0.0.0.0
Restart=on-failure
RestartSec=5
# --- hardening: lihat systemd.exec(5) ---
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
cat > proyek/config/backup-keluarga.service <<'EOF'
# backup-keluarga.service — unit oneshot untuk backup harian.
# Skrip yang dijalankan adalah hasil Proyek Skrip Backup Otomatis,
# dipasang sebagai /usr/local/bin/backup-keluarga.sh pada server.
# Unit ini tidak punya [Install]: hanya timer yang menariknya.
[Unit]
Description=Backup harian data keluarga (foto dan dokumen)
After=local-fs.target
[Service]
Type=oneshot
User=berbagi
Group=berbagi
ExecStart=/bin/bash /usr/local/bin/backup-keluarga.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
NoNewPrivileges=true
PrivateTmp=true
EOF
cat > proyek/config/backup-keluarga.timer <<'EOF'
# backup-keluarga.timer — jadwal harian untuk backup-keluarga.service.
# Aktifkan dengan: systemctl enable --now backup-keluarga.timer
[Unit]
Description=Jalankan backup keluarga setiap hari jam 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
Unit=backup-keluarga.service
[Install]
WantedBy=timers.target
EOF
cat > proyek/config/home-server.conf <<'EOF'
# home-server.conf — konfigurasi status-home-server.sh.
# Sunting nilai di bawah ini sesuai server Anda; skrip tidak perlu
# disentuh. Jalur relatif dihitung dari direktori kerja saat skrip
# dijalankan; jalur absolut (mis. /srv/backup/run) juga boleh dipakai.
# Port yang didengarkan layanan berbagi file.
PORT_BERBAGI=8765
# Mount point disk data — harus cocok dengan kolom terakhir output df.
DATA_MOUNT=/srv/data
# Folder yang berisi run backup-YYYYMMDD (lihat Proyek Skrip Backup
# Otomatis). Di server sungguhan biasanya /srv/backup/run.
BACKUP_DIR=srv/backup/run
# Backup dianggap terlalu tua bila umurnya melewati nilai ini (hari).
BACKUP_MAX_UMUR_HARI=2
# Ambang pemakaian disk data (%) yang menyalakan temuan (1-99).
DISK_AMBANG_PCT=85
# Berkas log temuan (opsional; default: status-home-server.log).
LOG_FILE=srv/backup/status-home-server.log
EOF
cat > proyek/deploy/cek-kesiapan.sh <<'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"
BASH
cat > proyek/deploy/status-home-server.sh <<'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"
BASH
cat > proyek/tests/run-tests.sh <<'TESTS'
#!/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 ]
TESTS
bash proyek/tests/run-tests.shOutput yang Dihasilkan:
OK berbagi-file.service lolos systemd-analyze verify
OK unit backup lolos systemd-analyze verify
OK unit berbagi berjalan sebagai user layanan non-root
OK WorkingDirectory mengarah ke folder berbagi
OK ExecStart menjalankan layanan pada port 8765
OK hardening ProtectSystem dan PrivateTmp aktif
OK timer memuat OnCalendar
OK timer Persistent mengejar jadwal yang terlewat
OK layanan mini siap menerima permintaan
OK berkas dijawab HTTP 200
OK isi berkas utuh sampai klien
OK ss melihat port 8765 dalam keadaan LISTEN
OK layout siap lolos pemeriksaan
OK laporan kesiapan menyatakan siap
OK mode salah dan nofail hilang terdeteksi (exit 2)
OK port 8765 bebas setelah layanan berhenti
OK fixture sehat keluar 0
OK laporan menyatakan layanan JALAN
OK disk data terbaca 72% dari snapshot
OK umur backup terhitung dari tanggal acuan
OK layanan mati menyalakan bit 2
OK disk 91% melewati ambang menyalakan bit 1
OK disk 91% dan backup tua menyalakan bit 1+4
OK tiga temuan menyalakan bit 1+2+4
OK laporan menyebut tiga temuan
OK umur backup tua dihitung benar
OK temuan tercatat ke berkas log
PASSED: 27/27 test lulusPola pentingnya:
systemd-analyze verifypada berkas sungguhan: suite memvalidasi persis unit yang akan dipasang, bukan salinan yang disederhanakan — kesalahan ketik pada unit tertangkap di sini, sebelum menyentuh/etc/systemd/system.grep '^User=berbagi$'dan kawan-kawan: directive penting diperiksa sebagai baris utuh. Unit yang “lolos verify” tapi kehilanganUser=akan tertangkap di lapis kedua ini — verify memeriksa kevalidan, grep memeriksa maksud kita.- Layanan sungguhan, bukan tiruan: suite menyalakan
http.serversungguhan, menunggu siap lewat loopcurl, memeriksa kode 200 dan isi berkas, melihatLISTENlewatss, lalu memastikan port bebas setelah berhenti — persis bukti manual dari Bagian 2, kini berjalan otomatis. - Fixture mtime beku: run backup dibuat dengan nama bertanggal sehingga umurnya pasti;
ss.txtdibekukan dari kondisi jalan dan mati;df.txtdiuji pada 72% dan 91%. Tanggal acuanHARI_INI=2026-02-15membuat seluruh perhitungan deterministik. - Bitmask diuji satu per satu: suite memastikan tiap temuan menyala bitnya sendiri (exit 1, 2, 4), lalu kombinasinya (5, 7) — kontrak kode keluar yang dijanjikan skrip terbukti untuk setiap cabangnya.
Home server kita selesai: fondasi yang siap diperiksa, layanan yang tervalidasi dan terbukti menjawab, jadwal backup yang mengejar yang terlewat, dan pengawas yang melapor dengan kode yang bisa dibaca mesin. Tetapi kita baru menyentuh satu pola penjadwalan — dunia cron dan systemd timer masih punya banyak bentuk lain, dari @reboot sampai timer dengan RandomizedDelaySec, dan itulah jalan belajar berikutnya di Otomasi dengan cron dan systemd
.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
deploy/cek-kesiapan.sh
Unduh berkas#!/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#!/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#!/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 ]