Home server yang gagal biasanya bukan gagal karena layanannya jelek, melainkan karena fondasinya amburadul: IP yang berubah tiap router restart sehingga keluarga kehilangan akses, disk data yang lupa dikonfigurasi sehingga boot tersangkut, dan layanan yang berjalan sebagai root karena “biar gampang”. Pada bagian ini kita menata fondasinya dulu — peran server, sistem operasi, alamat IP yang stabil, akses SSH, disk data terpisah, struktur /srv, dan user khusus untuk layanan. Setiap keputusan ditutup dengan pemeriksaan yang bisa dijalankan di lingkungan latihan: fixture fstab diperiksa dengan findmnt, permission diperiksa dengan stat, dan seluruhnya dirangkum oleh skrip cek kesiapan.
Peran Server dan Perangkatnya #
Satu keputusan pertama menentukan semuanya: server ini untuk apa? Kita tetapkan perannya sederhana — penyimpanan file keluarga dan backup otomatis, bukan hosting publik. Dengan peran sesempit itu, kebutuhan perangkatnya rendah, dan justru perangkat “sampah” di rumah yang menang:
| Perangkat | Kenapa cocok | Catatan |
|---|---|---|
| mini PC bekas kantor | hemat daya, kecil, diam | pilihan paling seimbang |
| laptop tua berlayar rusak | gratis, punya baterai = UPS bawaan | layar tidak diperlukan |
| Raspberry Pi | hemat daya, mudah didapat | hindarkan dari media transcode |
Prinsip memilih dan menghidupkan perangkat semacam ini — dari mengukur pemakaian RAM sampai memilih distro ringan — sudah kita bahas tuntas di Memilih Distro Ringan . Yang perlu diingat di sini: server ini menyala terus, jadi daya dan kebisingan ikut menentukan kenyamanan rumah, bukan hanya angka benchmark.
Sistem Operasi Tanpa GUI #
Untuk server, kita pakai Ubuntu Server atau Debian tanpa desktop environment. Alasannya bukan tradisi, melainkan hitungan: GUI memakan ratusan MB RAM yang tidak membeli apa pun untuk server yang diakses lewat SSH, menambah permukaan serangan karena komponen yang terpasang lebih banyak, dan menambah pekerjaan update. Instalasinya dari ISO resmi, dan setelah sistem pertama kali hidup, dua perintah ini memastikan paketnya mutakhir:
sudo apt update && sudo apt full-upgradeSatu-satunya paket tambahan yang kita butuhkan di awal adalah server SSH, karena seluruh pengelolaan selanjutnya kita lakukan dari laptop:
sudo apt install openssh-serverIP Stabil Lewat DHCP Reservation #
Server yang alamatnya berubah-ubah adalah server yang tidak bisa diandalkan keluarganya: bookmark http://192.0.2.10:8765/ mati diam-diam tiap beberapa hari. Kita mengunci alamat itu lewat fitur router bernama DHCP reservation — router tetap memakai DHCP, tetapi untuk MAC address tertentu selalu membagikan IP yang sama. Contoh tabelnya di halaman admin router:
| Perangkat | MAC address | IP yang dikunci |
|---|---|---|
| home-server | dc:a6:32:1a:2b:3c | 192.0.2.10 |
| laptop-budi | a4:5e:60:d1:8f:02 | 192.0.2.21 |
Akses dari Jauh: SSH #
Seluruh sisa proyek ini dikerjakan lewat SSH dari laptop. Kuncinya dibuat sekali di laptop, lalu ditempelkan ke server — password login kemudian bisa dimatikan:
ssh-keygen -t ed25519 -C "budi@laptop-budi"
ssh-copy-id [email protected]
ssh [email protected]Perintah pertama membuat pasangan kunci di ~/.ssh/, yang kedua menempelkan kunci publik ke server, dan yang ketiga adalah cara kita bekerja mulai sekarang: terminal yang sama seperti biasa, hanya saja berjalan di mesin di ruang sebelah. Mekanisme kunci dan opsi sshd yang lebih ketat dibahas di ssh dan scp
; untuk proyek ini login kunci saja sudah cukup.
Diska Data Terpisah dan fstab nofail #
Sistem operasinya boleh memakai disk apa pun, tetapi data keluarga harus hidup di disk terpisah — foto dan backup membesar tanpa batas, sedangkan partisi sistem butuh ruang napasnya sendiri. Disk data kita pasang di /srv/data lewat /etc/fstab, dan opsi kuncinya adalah nofail: bila disk itu suatu hari mati, server tetap boot dan kita masih bisa masuk lewat SSH untuk memperbaiki — tanpa nofail, boot tersangkut menunggu disk yang tidak akan pernah datang. Struktur lengkap berkas fstab dan arti tiap kolomnya sudah dibedah di fstab
; di sini kita fokus pada cara memvalidasinya. Karena lingkungan latihan tidak punya disk sungguhan, kita bekerja pada fixture berkas — tetapi pemeriksanya, findmnt --verify, adalah alat yang sama yang akan Anda jalankan di server:
mkdir -p latihan/srv/data/tmp
printf 'UUID=5c1b2a3d-1111-4222-8333-444455556666 /srv/data ext4 defaults,nofail 0 2\n' > latihan/fstab.data
cat > latihan/fstab.tmpfs <<EOF
tmpfs $PWD/latihan/srv/data/tmp tmpfs size=256M,nosuid,nodev,noexec 0 0
EOF
echo "== findmnt memeriksa entri tmpfs (targetnya benar-benar ada) =="
findmnt --verify -F latihan/fstab.tmpfs
echo "exit=$?"
echo "== findmnt menolak target yang tidak ada =="
printf 'tmpfs /srv/data/tidak-ada tmpfs defaults 0 0\n' > latihan/fstab.salah
findmnt --verify -F latihan/fstab.salah 2>&1
echo "exit=$?"Output yang Dihasilkan:
== findmnt memeriksa entri tmpfs (targetnya benar-benar ada) ==
Success, no errors or warnings detected
exit=0
== findmnt menolak target yang tidak ada ==
0 parse errors, 1 error, 0 warnings
/srv/data/tidak-ada
[E] unreachable on boot required target: No such file or directory
exit=1Bedah blok di atas:
latihan/fstab.dataadalah entri disk data yang akan kita tulis ke/etc/fstabdi server sungguhan — sumbernyaUUID(bukan/dev/sdb1yang bisa berubah urutan), targetnya/srv/data, opsidefaults,nofail, dan kolom fsck2karena bukan root filesystem. Detail tiap kolom ada di fstab(5) .latihan/fstab.tmpfssengaja memakai jalur absolut dari folder latihan sebagai target —findmnt --verifymemeriksa apakah target itu benar-benar ada, jadi fixture latihan harus menunjuk folder yang bisa dibuat di sini. Entri tmpfs untuk ruang sementaranya disk data itu contoh entri tambahan yang sah di server sungguhan.findmnt --verify -F <berkas>: memeriksa berkas tabel mount yang kita tunjuk (-F, singkatan--tab-file) tanpa menyentuh/etc/fstabsungguhan. Entri tmpfs lolos karena targetnya ada; opsi lengkapnya di findmnt(8) .- Entri dengan target yang tidak ada ditolak keras:
unreachable on boot required targetberarti saat boot,mountakan gagal menemukan foldernya — persis kelas kesalahan yang ingin kita tangkap sebelum server dinyalakan, bukan sesudah. - Di server sungguhan,
sudo findmnt --verifytanpa-Fmemeriksa/etc/fstabasli termasuk mengecek apakahUUIDbenar-benar terpasang — bagian yang tidak bisa kita tiru di fixture karena lingkungan latihan tidak punya block device.
Struktur /srv dan User Layanan Non-Root #
Semua yang dilayankan server ini hidup di bawah /srv — lokasi standar untuk data layanan menurut hierarki filesystem. Kita pakai empat folder: berbagi untuk file yang dibagikan, foto-keluarga untuk foto yang dikumpulkan dari ponsel, backup untuk hasil backup, dan data untuk mount disk data. Yang memilikinya bukan user login kita, melainkan user sistem khusus:
sudo useradd -r -m -d /srv -s /usr/sbin/nologin berbagi
sudo mkdir -p /srv/berbagi /srv/foto-keluarga /srv/backup
sudo chown berbagi:berbagi /srv/berbagi /srv/foto-keluarga
sudo chmod 750 /srv/berbagiMengapa repot-repot membuat user khusus? Karena setiap layanan yang berjalan mewarisi hak akses user pemiliknya. Layanan berjalan sebagai root berarti bug atau diretasnya layanan itu langsung menjadi bencana seluruh sistem; berjalan sebagai user berbagi berarti ledakannya terbatas pada folder yang memang ia kelola. Opsi -r membuat user sistem (tanpa masa berlaku password), -m membuat home directory /srv, dan -s /usr/sbin/nologin memastikan user ini tidak bisa dipakai login — layanan meminjam identitasnya, bukan sebaliknya; rincian opsinya di useradd(8)
. Di lingkungan latihan kita bangun bentuk yang sama tanpa root — folder di-recreate, permission disetel dengan chmod, dan kepemilikan diperiksa relatif terhadap user saat ini:
mkdir -p latihan/srv/berbagi latihan/srv/foto-keluarga latihan/srv/backup
printf 'jadwal piknik keluarga\n' > latihan/srv/berbagi/jadwal-piknik.txt
chmod 750 latihan/srv/berbagi
chmod 755 latihan/srv latihan/srv/foto-keluarga latihan/srv/backup
tree latihan/srv
stat -c '%a %n' latihan/srv/berbagi latihan/srv/foto-keluarga latihan/srv/backup
[ -O latihan/srv/berbagi ] && echo "folder berbagi dimiliki user saat ini"Output yang Dihasilkan:
latihan/srv
├── backup
├── berbagi
│ └── jadwal-piknik.txt
└── foto-keluarga
4 directories, 1 file
750 latihan/srv/berbagi
755 latihan/srv/foto-keluarga
755 latihan/srv/backup
folder berbagi dimiliki user saat iniTiga hal dari output itu yang akan dicek otomatis nanti: mode 750 pada berbagi (pemilik baca-tulis, grup baca, lainnya tidak bisa lihat sama sekali), mode 755 pada folder data lain (perlu dibaca layanan lain), dan kepemilikan oleh user layanan — [ -O path ] menguji kepemilikan tanpa harus mencetak nama user, supaya pemeriksaannya bisa dijalankan oleh siapa pun.
Skrip Cek Kesiapan #
Keputusan-keputusan di atas berguna hanya bila ada cara murah memastikannya terpenuhi. Skrip berikut memeriksa struktur /srv, mode, kepemilikan, dan entri fstab dalam satu eksekusi — semua targetnya bisa digeser lewat variabel lingkungan supaya pemeriksaan bisa dilatih pada fixture. Versi lengkapnya tersedia sebagai deploy/cek-kesiapan.sh pada bagian Berkas Sumber di bawah halaman ini:
mkdir -p latihan/srv/berbagi latihan/srv/foto-keluarga latihan/srv/backup
printf 'jadwal piknik keluarga\n' > latihan/srv/berbagi/jadwal-piknik.txt
chmod 750 latihan/srv/berbagi
printf 'UUID=5c1b2a3d-1111-4222-8333-444455556666 /srv/data ext4 defaults,nofail 0 2\n' > latihan/fstab.data
cat > 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
echo "== layout siap =="
SRV_ROOT=latihan/srv FSTAB_FILE=latihan/fstab.data bash cek-kesiapan.sh
echo "exit=$?"
echo "== layout rusak: mode salah dan nofail hilang =="
chmod 755 latihan/srv/berbagi
sed 's/,nofail//' latihan/fstab.data > latihan/fstab.rusak
SRV_ROOT=latihan/srv FSTAB_FILE=latihan/fstab.rusak bash cek-kesiapan.sh
echo "exit=$?"Output yang Dihasilkan:
== layout siap ==
OK folder latihan/srv/berbagi (mode 750)
OK pemilik latihan/srv/berbagi sesuai user layanan
OK folder latihan/srv/foto-keluarga
OK folder latihan/srv/backup
OK entri fstab untuk /srv/data memuat nofail
server siap dijalankan: 0 temuan
exit=0
== layout rusak: mode salah dan nofail hilang ==
GAGAL folder latihan/srv/berbagi mode 755, harapan 750
OK pemilik latihan/srv/berbagi sesuai user layanan
OK folder latihan/srv/foto-keluarga
OK folder latihan/srv/backup
GAGAL fstab belum memuat entri nofail untuk /srv/data
server belum siap: 2 temuan
exit=2Pola pentingnya:
SRV_ROOT=... FSTAB_FILE=... bash cek-kesiapan.sh: variabel lingkungan ditempel di depan perintah untuk eksekusi itu saja — default/srvdan/etc/fstabtetap berlaku di server sungguhan tanpa menyunting skrip.stat -c %adanstat -c %U: permission oktal dan nama pemilik dibaca tanpa waktu modifikasi, sehingga hasilnya stabil dan cocok dibandingkan.awk ... $4 ~ /(^|,)nofail(,|$)/: opsinofaildicocokkan sebagai satu token utuh — opsi palsu sepertimynofailxtidak lolos mengakunofail, dan baris komentar di fstab diabaikan lewat$0 !~ /^[[:space:]]*#/.exit "$temuan": jumlah temuan menjadi exit code — 0 berarti siap, 2 berarti dua hal harus dibereskan. Pada sesi rusak di atas,chmod 755dan hilangnyanofailsengaja dibuat supaya bentuk laporan kegagalannya kelihatan.
Fondasi kini berdiri: alamat yang stabil, akses SSH, disk data yang aman dari boot gagal, struktur /srv yang jelas, dan user layanan yang terpisah dari kehidupan root. Folder-folder itu masih kosong dan belum ada yang melayani — pekerjaan Bagian 2 adalah menghidupkan layanan pertama sebagai unit systemd yang sah.
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 ]