File server yang tumbuh tanpa rencana biasanya berakhir sebagai satu folder raksasa berisi campuran dokumen kerja, foto liburan, dan salinan backup yang saling menimpa — dan permission? Semuanya 777, karena “biar tidak ribet”. Bagian ini mencegah kekacauan itu sejak awal: kita menetapkan share per fungsi, memisahkan data ke disk sendiri, lalu membangun struktur folder beserta grup dan mode permission-nya lewat skrip yang bisa dijalankan ulang. Tiga artefak hasil bagian ini — tabel rancangan, entri fstab, dan struktur /srv/share — menjadi acuan langsung bagi konfigurasi Samba dan NFS pada Bagian 2.
Merancang Struktur Share #
Lokasi baku untuk data layanan di Linux adalah /srv — jadi share kita hidup di bawah /srv/share. Sebelum mengetik perintah apa pun, seluruh rancangan kita tuangkan dulu ke satu tabel:
| Share | Isi | Grup pemilik | Mode di disk | Tamu |
|---|---|---|---|---|
/srv/share/dokumen | dokumen kerja dan catatan | keluarga | 2770 | ditolak |
/srv/share/media | foto dan video keluarga | keluarga | 2775 | baca saja |
/srv/share/backup | arsip backup dari laptop | backup | 2770 | ditolak |
Tiga keputusan tersembunyi di tabel itu. Pertama, pemisahan per fungsi: media tumbuh besar dan jarang diubah, dokumen kecil tapi sibuk diedit, dan backup tidak boleh tersentuh oleh pembersihan rutin — mencampurnya dalam satu folder membuat ketiganya saling mengganggu. Kedua, mode 2770 dan 2775 sama-sama memasang bit setgid (digit 2) agar berkas baru diwariskan ke grup foldernya — mekanisme ini kita buktikan hidupnya di Bagian 3; perbedaannya hanya pada kelas other: 0 menutup akses tamu sepenuhnya, 5 mengizinkan mereka membaca share media. Ketiga, share backup diberi grup sendiri (backup) karena hanya admin yang boleh membaca-menulis isinya — anggota keluarga yang butuh file lamanya meminta bantuan admin, bukan menghapus-rapikan sendiri.
Mode oktal dan kelas owner/group/other sudah kita kenal dari Chapter File Permissions ; digit keempat milik setgid dibahas di Chapter Special Permissions .
Disk Data Terpisah #
Satu keputusan struktural menyelamatkan banyak server rumahan: data hidup di disk (atau partisi) terpisah dari sistem. Saat sistem operasi diinstall ulang — yang di server rumah bisa terjadi saat eksperimen gagal — disk data tidak ikut terformat; cukup dipasang kembali ke mount point yang sama dan seluruh share kembali hidup. Pola partisinya sederhana: satu disk untuk /, satu disk data ber-filesystem ext4 yang dipasang di /srv/share.
Disk data terpisah juga mengubah ukuran masalah: ruang penyimpanan bisa ditambah dengan disk baru tanpa memperbesar partisi sistem, dan penuhnya disk data “hanya” membuat share berhenti menerima berkas — sistem tetap bisa login dan memberi peringatan.
Perintah persiapan di server dijalankan sekali dan membutuhkan root — lsblk -f untuk melihat nama disk dan UUID-nya, mkfs.ext4 untuk memformat, lalu direktori mount point dibuat:
lsblk -f
sudo mkfs.ext4 /dev/sdb1
sudo mkdir -p /srv/sharePemasangan permanen diatur /etc/fstab — cara kerja keenam field-nya sudah dibedah tuntas di Chapter fstab
. Berkas berikut adalah entri kita, ditulis persis seperti yang ditempelkan ke /etc/fstab di server; versi lengkapnya tersedia sebagai config/fstab-share pada bagian Berkas Sumber di bawah halaman ini:
mkdir -p config
cat > config/fstab-share <<'FSTAB'
# fstab-share — entri disk data untuk file server.
#
# Salin baris di bawah ini ke /etc/fstab di server, lalu terapkan:
# sudo mount -a && findmnt /srv/share
#
# Ganti UUID dengan disk data Anda (lihat daftarnya: lsblk -f).
# Subnet 192.0.2.0/24 dipakai sebagai contoh blok dokumentasi.
UUID=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d /srv/share ext4 defaults,nofail 0 2
FSTAB
cat config/fstab-shareOutput yang Dihasilkan:
# fstab-share — entri disk data untuk file server.
#
# Salin baris di bawah ini ke /etc/fstab di server, lalu terapkan:
# sudo mount -a && findmnt /srv/share
#
# Ganti UUID dengan disk data Anda (lihat daftarnya: lsblk -f).
# Subnet 192.0.2.0/24 dipakai sebagai contoh blok dokumentasi.
UUID=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d /srv/share ext4 defaults,nofail 0 2Mari kita bedah keenam field baris aktifnya:
UUID=a1b2...4c5d: identitas disk yang stabil — nama perangkat/dev/sdb1bisa bergeser saat urutan boot berubah, UUID tidak. Nilai di atas contoh; ganti dengan outputlsblk -fdi server Anda./srv/share: mount point tempat isi disk muncul di pohon direktori.ext4: jenis filesystem yang kita pilih saatmkfs.ext4.defaults,nofail: opsi standar plusnofail— bila disk data mati atau lupa dipasang, boot tidak menggantung menunggunya; sistem tetap naik dan masalah bisa diperbaiki lewat SSH.0: urutan dump, warisan backup lawas yang sudah tidak dipakai modern systemd.2: urutanfsck— disk data diperiksa setelah root (1), bukan bersamaan.
Setelah sudo mount -a, perintah ls -a /srv/share memperlihatkan satu folder yang tidak kita buat: lost+found. Itu bukan penyusup — folder ini dibuat mkfs.ext4 di akar filesystem sebagai tempat fsck menaruh berkas yang ditemukannya tanpa induk setelah kerusakan disk. Ia dimiliki root dengan mode 0700, ukurannya bisa beberapa megabyte, dan sebaiknya dibiarkan apa adanya; karena share kita adalah subfolder /srv/share/dokumen dan kawan-kawannya, lost+found tidak pernah muncul di dalam share mana pun.
Validasi Entri fstab #
Entri fstab yang salah salah satu field-nya bisa membuat server gagal boot atau justru mengabaikan disk data, jadi layak diperiksa dengan mesin, bukan mata. Di server, alat resminya adalah sudo findmnt --fstab --verify; di latihan kita cukup awk pada berkasnya. Blok berikut menulis ulang entri utama sebagai fixture satu baris, mendefinisikan pemeriksanya, lalu menguji empat sifat entri aktif — jumlah field, sumber UUID, kehadiran nofail, dan urutan fsck — termasuk pada entri yang sengaja dibolongi:
periksa_fstab() {
awk '
!/^[[:space:]]*#/ && NF {
if (NF != 6) printf "GAGAL %d field (harapan 6): %s\n", NF, $2
else if ($1 !~ /^UUID=/) printf "GAGAL sumber bukan UUID: %s\n", $1
else if ($4 !~ /nofail/) printf "GAGAL tanpa nofail: %s\n", $2
else if ($6 != "2") printf "GAGAL urutan fsck: %s\n", $6
else printf "OK %s -> %s\n", $1, $2
}' "$1"
}
printf '%s\n' \
'UUID=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d /srv/share ext4 defaults,nofail 0 2' \
> fstab-share
echo "== entri utama =="
periksa_fstab fstab-share
echo "== entri tanpa nofail =="
printf 'UUID=bebas-apa-pun /srv/share ext4 defaults 0 1\n' > fstab-rusak
periksa_fstab fstab-rusakOutput yang Dihasilkan:
== entri utama ==
OK UUID=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d -> /srv/share
== entri tanpa nofail ==
GAGAL tanpa nofail: /srv/shareBedahnya singkat:
- Pola
!/^[[:space:]]*#/ && NF: hanya baris aktif yang diperiksa — komentar dan baris kosong dilewati, sama seperti pola audit di Chapter NFS . - Rantai
if/else if: entri diperiksa dari kesalahan paling fatal; pesan pertama yang cocok cukup, karena satu entri rusak diperbaiki satu kesalahan demi satu kesalahan. $4 !~ /nofail/: opsi mount diperiksa sebagai substring dari field keempat, sehinggadefaults,nofailmaupunnofail,defaultssama-sama lolos.
Entri kedua memang lolos dua pemeriksaan pertama hanya untuk tertangkap di nofail — dan itulah kegunaan checker: kesalahan yang paling sering dilupakan manusia adalah yang paling murah ditangkap mesin. Rincian semua field dan opsi lain ada di fstab(5)
.
Membangun Struktur dengan Skrip #
Rancangan kini diterjemahkan menjadi struktur sungguhan. Alasnya skrip, bukan deretan perintah manual: struktur harus bisa dibangun ulang dengan hasil identik saat server diganti atau disk data dipasang ulang. Skrip berikut — identik dengan deploy/buat-struktur.sh — membaca rencana dari satu variabel, membuat ketiga folder, mengatur grup dan mode, lalu memverifikasi hasilnya dengan stat. Saat berjalan sebagai root ia membuat grup sungguhan lewat groupadd; sebagai user biasa, ia berjalan dalam mode simulasi: struktur dan permission tetap asli, sedangkan chgrp ke grup keluarga digantikan ke grup utama kita dan rencana grupnya dicatat sebagai fixture:
cat > buat-struktur.sh <<'BASH'
#!/usr/bin/env bash
# buat-struktur.sh — bangun struktur penyimpanan file server.
#
# Dua mode eksekusi:
# - sebagai root di server : membuat group sungguhan (groupadd bila
# belum ada), folder di bawah TARGET, dan chgrp ke group aslinya.
# - sebagai user biasa : simulasi — folder tetap dibuat, chgrp
# digantikan ke group utama kita, dan rencana group dicatat ke
# TARGET/etc/group sebagai fixture yang bisa dibaca ulang.
#
# Pemakaian:
# sudo bash buat-struktur.sh /srv # di server sungguhan
# bash buat-struktur.sh latihan # latihan lokal tanpa root
#
# Kode keluar:
# 0 sukses 1 argumen tidak valid
# 2 struktur gagal dibuat/tidak sesuai
set -u
TARGET="${1:-latihan}"
# Rencana share: nama:group:mode (mode 4 digit oktal, setgid aktif).
RENCANA="dokumen:keluarga:2770 media:keluarga:2775 backup:backup:2770"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
printf 'buat-struktur.sh: target tidak valid: %s\n' "$TARGET" >&2
exit 1
fi
if [ "$TARGET" = "/srv" ] && [ "$(id -u)" -ne 0 ]; then
printf 'buat-struktur.sh: /srv milik root; jalankan dengan sudo\n' >&2
exit 1
fi
mkdir -p "$TARGET" || exit 1
SHARE_DIR="$TARGET/share"
# --- 1. Siapkan group ----------------------------------------------------
if [ "$(id -u)" -eq 0 ]; then
printf 'mode : server (root)\n'
for grup in keluarga tamu backup; do
if ! getent group "$grup" > /dev/null; then
groupadd "$grup" || exit 2
fi
done
else
printf 'mode : simulasi tanpa root\n'
mkdir -p "$TARGET/etc" || exit 2
cat > "$TARGET/etc/group" <<'GRUP'
keluarga:x:1100:budi,siti
tamu:x:1101:tamu1
backup:x:1102:budi
GRUP
printf 'group : keluarga, tamu, backup dicatat di %s/etc/group\n' "$TARGET"
fi
# --- 2. Buat folder share beserta permission-nya -------------------------
printf 'struktur: %s/{dokumen,media,backup}\n' "$SHARE_DIR"
gagal=0
for entri in $RENCANA; do
nama=${entri%%:*}
sisa=${entri#*:}
grup=${sisa%%:*}
mode=${sisa##*:}
dir="$SHARE_DIR/$nama"
mkdir -p "$dir" || { gagal=$((gagal + 1)); continue; }
if [ "$(id -u)" -eq 0 ]; then
chgrp "$grup" "$dir" || { gagal=$((gagal + 1)); continue; }
else
# Tanpa root kita hanya boleh memindahkan group ke group sendiri;
# di server, baris ini menjadi: chgrp "$grup" "$dir"
chgrp "$(id -gn)" "$dir" || { gagal=$((gagal + 1)); continue; }
fi
chmod "$mode" "$dir"
# --- 3. Verifikasi hasil dengan stat -------------------------------
nyata=$(stat -c '%a' "$dir")
if [ "$nyata" != "$mode" ]; then
printf 'GAGAL %s mode %s, harapan %s\n' "$dir" "$nyata" "$mode"
gagal=$((gagal + 1))
else
printf 'OK %s %s\n' "$mode" "$dir"
fi
done
if [ "$gagal" -gt 0 ]; then
printf 'GAGAL: %d pemeriksaan tidak sesuai\n' "$gagal"
exit 2
fi
printf 'selesai tanpa error\n'
BASH
bash buat-struktur.sh latihan
echo "== struktur akhir =="
tree latihan
echo "== fixture group simulasi =="
cat latihan/etc/group
echo "== mode ketiga share =="
stat -c '%a %n' latihan/share/*Output yang Dihasilkan:
mode : simulasi tanpa root
group : keluarga, tamu, backup dicatat di latihan/etc/group
struktur: latihan/share/{dokumen,media,backup}
OK 2770 latihan/share/dokumen
OK 2775 latihan/share/media
OK 2770 latihan/share/backup
selesai tanpa error
== struktur akhir ==
latihan
├── etc
│ └── group
└── share
├── backup
├── dokumen
└── media
6 directories, 1 file
== fixture group simulasi ==
keluarga:x:1100:budi,siti
tamu:x:1101:tamu1
backup:x:1102:budi
== mode ketiga share ==
2770 latihan/share/backup
2770 latihan/share/dokumen
2775 latihan/share/mediaMari kita bedah bagian-bagian kuncinya:
- Variabel
RENCANA: seluruh rancangan tabel di awal bagian ini hidup sebagai satu baris datanama:group:mode. Menambah share baru berarti menambah satu entri — logika di bawahnya tidak berubah. Ini pola yang sama dengan berkas konfigurasi terpisah pada Proyek Skrip Backup Otomatis : data dipisah dari kode. - Pemecahan
${entri%%:*}/${sisa#*:}: parameter expansion memenggal tiap entri menjadi nama, grup, dan mode tanpa memanggil perintah eksternal. - Cabang
id -u: satu skrip untuk dua dunia. Di server,getent groupmemeriksa keberadaan grup sebelumgroupaddsupaya skrip aman dijalankan ulang. Di latihan,chgrp "$(id -gn)"adalah pengganti jujur: user biasa hanya boleh memindahkan grup ke grup tempatnya menjadi anggota — batas ini kita saksikan lagi di Bagian 3. - Verifikasi
stat -c '%a': skrip tidak berhenti pada “sudah menjalankan chmod”, melainkan membaca ulang mode dari disk dan membandingkannya dengan rencana. Angka2770dan2775di output adalah bukti bit setgid benar-benar terpasang. exit 2saat verifikasi gagal: test suite nanti bisa membedakan “struktur gagal dibuat” dari “argumen salah” (exit 1) hanya lewat kode keluar.
Di server sungguhan, perintahnya sama persis dengan di atas: sudo bash buat-struktur.sh /srv — grup keluarga, tamu, dan backup dibuat jika belum ada, ketiga folder dipindahkan ke grupnya masing-masing, lalu mode dipasang. Fixture latihan/etc/group di latihan meniru baris yang nanti ditambahkan groupadd ke /etc/group sungguhan — format kolomnya sudah kita kenal dari Chapter Group Management
.
Fondasi kini berdiri: disk data dengan entri fstab yang aman, dan tiga share dengan grup serta mode yang terverifikasi stat. Namun sejauh ini semuanya masih hanya terlihat dari dalam server sendiri. Pekerjaan Bagian 2: membagikannya ke jaringan — Samba untuk klien Windows dan ponsel, NFS untuk klien Linux — dengan konfigurasi yang sejalan dengan permission yang barusan kita pasang.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
deploy/buat-struktur.sh
Unduh berkas#!/usr/bin/env bash
# buat-struktur.sh — bangun struktur penyimpanan file server.
#
# Membuat tiga share (dokumen, media, backup), mengatur group dan
# mode permission-nya, lalu memverifikasi hasil akhirnya dengan stat.
#
# Dua mode eksekusi:
# - sebagai root di server : membuat group sungguhan (groupadd bila
# belum ada), folder di bawah TARGET, dan chgrp ke group aslinya.
# - sebagai user biasa : simulasi — folder tetap dibuat, chgrp
# digantikan ke group utama kita, dan rencana group dicatat ke
# TARGET/etc/group sebagai fixture yang bisa dibaca ulang.
#
# Pemakaian:
# sudo bash buat-struktur.sh /srv # di server sungguhan
# bash buat-struktur.sh latihan # latihan lokal tanpa root
#
# Kode keluar:
# 0 sukses 1 argumen tidak valid
# 2 struktur gagal dibuat/tidak sesuai
set -u
TARGET="${1:-latihan}"
# Rencana share: nama:group:mode (mode 4 digit oktal, setgid aktif).
RENCANA="dokumen:keluarga:2770 media:keluarga:2775 backup:backup:2770"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
printf 'buat-struktur.sh: target tidak valid: %s\n' "$TARGET" >&2
exit 1
fi
if [ "$TARGET" = "/srv" ] && [ "$(id -u)" -ne 0 ]; then
printf 'buat-struktur.sh: /srv milik root; jalankan dengan sudo\n' >&2
exit 1
fi
mkdir -p "$TARGET" || exit 1
SHARE_DIR="$TARGET/share"
# --- 1. Siapkan group ----------------------------------------------------
if [ "$(id -u)" -eq 0 ]; then
printf 'mode : server (root)\n'
for grup in keluarga tamu backup; do
if ! getent group "$grup" > /dev/null; then
groupadd "$grup" || exit 2
fi
done
else
printf 'mode : simulasi tanpa root\n'
mkdir -p "$TARGET/etc" || exit 2
cat > "$TARGET/etc/group" <<'GRUP'
keluarga:x:1100:budi,siti
tamu:x:1101:tamu1
backup:x:1102:budi
GRUP
printf 'group : keluarga, tamu, backup dicatat di %s/etc/group\n' "$TARGET"
fi
# --- 2. Buat folder share beserta permission-nya -------------------------
printf 'struktur: %s/{dokumen,media,backup}\n' "$SHARE_DIR"
gagal=0
for entri in $RENCANA; do
nama=${entri%%:*}
sisa=${entri#*:}
grup=${sisa%%:*}
mode=${sisa##*:}
dir="$SHARE_DIR/$nama"
mkdir -p "$dir" || { gagal=$((gagal + 1)); continue; }
if [ "$(id -u)" -eq 0 ]; then
chgrp "$grup" "$dir" || { gagal=$((gagal + 1)); continue; }
else
# Tanpa root kita hanya boleh memindahkan group ke group sendiri;
# di server, baris ini menjadi: chgrp "$grup" "$dir"
chgrp "$(id -gn)" "$dir" || { gagal=$((gagal + 1)); continue; }
fi
chmod "$mode" "$dir"
# --- 3. Verifikasi hasil dengan stat -------------------------------
nyata=$(stat -c '%a' "$dir")
if [ "$nyata" != "$mode" ]; then
printf 'GAGAL %s mode %s, harapan %s\n' "$dir" "$nyata" "$mode"
gagal=$((gagal + 1))
else
printf 'OK %s %s\n' "$mode" "$dir"
fi
done
if [ "$gagal" -gt 0 ]; then
printf 'GAGAL: %d pemeriksaan tidak sesuai\n' "$gagal"
exit 2
fi
printf 'selesai tanpa error\n'
deploy/verifikasi-share.sh
Unduh berkas#!/usr/bin/env bash
# verifikasi-share.sh — periksa directive wajib smb.conf dan format exports.
#
# Pengganti testparm/exportfs di lingkungan tanpa Samba dan NFS: menerima
# folder yang memuat smb.conf dan exports, lalu memeriksa directive
# keamanan yang kita rancang satu per satu dengan grep dan awk.
#
# Pemakaian:
# bash verifikasi-share.sh <folder-config>
#
# Kode keluar = jumlah pemeriksaan yang gagal.
set -u
if [ $# -ne 1 ]; then
printf 'pemakaian: verifikasi-share.sh <folder-config>\n' >&2
exit 1
fi
SMB="$1/smb.conf"
EXP="$1/exports"
gagal=0
for f in "$SMB" "$EXP"; do
if [ ! -f "$f" ]; then
printf 'verifikasi-share.sh: berkas tidak ditemukan: %s\n' "$f" >&2
exit 1
fi
done
ok() { printf 'OK %s\n' "$1"; }
hilang() { printf 'HILANG %s\n' "$1"; gagal=$((gagal + 1)); }
# --- smb.conf: section dan directive wajib --------------------------------
for pola in \
'[global]' \
'[dokumen]' \
'[media]' \
'[backup]' \
'path = /srv/share/dokumen' \
'path = /srv/share/media' \
'path = /srv/share/backup' \
'valid users = @keluarga' \
'read only = no' \
'create mask = 0660' \
'directory mask = 2770' \
'browseable = no' \
'bind interfaces only = yes'
do
if grep -qF -- "$pola" "$SMB"; then
ok "smb.conf: $pola"
else
hilang "smb.conf: $pola"
fi
done
# --- exports: format dan opsi keamanan -----------------------------------
# 1. Setiap entri aktif memuat kurung opsi di kolom klien.
n=$(awk '!/^[[:space:]]*#/ && NF && $2 !~ /\(/ { c++ } END { print c + 0 }' "$EXP")
if [ "$n" -eq 0 ]; then
ok 'exports: semua entri memuat opsi klien'
else
hilang 'exports: ada entri tanpa opsi klien'
fi
# 2. Setiap entri memuat sync dan no_subtree_check.
n=$(awk '
!/^[[:space:]]*#/ && NF && ($2 !~ /sync/ || $2 !~ /no_subtree_check/) { c++ }
END { print c + 0 }' "$EXP")
if [ "$n" -eq 0 ]; then
ok 'exports: semua entri memuat sync + no_subtree_check'
else
hilang 'exports: ada entri tanpa sync / no_subtree_check'
fi
# 3. root_squash tidak boleh dimatikan.
if grep -q 'no_root_squash' "$EXP"; then
hilang 'exports: no_root_squash terdeteksi'
else
ok 'exports: root_squash tetap aktif'
fi
# 4. dokumen rw, media ro.
mode_dokumen=$(awk '$1 == "/srv/share/dokumen" {
print ($2 ~ /\(rw/) ? "rw" : "ro" }' "$EXP")
if [ "$mode_dokumen" = "rw" ]; then
ok 'exports: dokumen dibagikan rw'
else
hilang 'exports: dokumen bukan rw'
fi
mode_media=$(awk '$1 == "/srv/share/media" {
print ($2 ~ /\(ro/) ? "ro" : "rw" }' "$EXP")
if [ "$mode_media" = "ro" ]; then
ok 'exports: media dibagikan ro'
else
hilang 'exports: media bukan ro'
fi
# 5. backup hanya untuk satu klien tepercaya.
klien=$(awk '$1 == "/srv/share/backup" {
k = $2; sub(/\(.*/, "", k); print k }' "$EXP")
if [ "$klien" = "192.0.2.10" ]; then
ok 'exports: backup terbatas ke 192.0.2.10'
else
hilang 'exports: klien backup bukan 192.0.2.10'
fi
exit "$gagal"
tests/run-tests.sh
Unduh berkas#!/usr/bin/env bash
# run-tests.sh — test suite untuk proyek server file lokal.
# Suite menyiapkan folder kerja terisolasi (mktemp), lalu menguji:
# struktur share hasil deploy/buat-struktur.sh (folder, mode, fixture
# group), directive wajib pada config/smb.conf dan config/exports
# lewat deploy/verifikasi-share.sh, entri config/fstab-share, pewarisan
# setgid pada folder share, serta penolakan tulis pada file read-only.
# 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
WORK=""
# 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
}
bersihkan() {
if [ -n "$WORK" ]; then
rm -rf "$WORK"
fi
}
trap bersihkan EXIT
WORK=$(mktemp -d)
cd "$WORK" || exit 1
# --- uji 1: buat-struktur.sh membangun struktur yang sesuai ---------------
bash "$PROJ_DIR/deploy/buat-struktur.sh" latihan > /dev/null 2>&1
assert_eq 0 "$?" "buat-struktur.sh keluar 0"
for nama in dokumen media backup; do
[ -d "latihan/share/$nama" ]
assert_eq 0 "$?" "share $nama dibuat"
done
assert_eq 2770 "$(stat -c '%a' latihan/share/dokumen)" \
"mode dokumen 2770"
assert_eq 2775 "$(stat -c '%a' latihan/share/media)" \
"mode media 2775"
assert_eq 2770 "$(stat -c '%a' latihan/share/backup)" \
"mode backup 2770"
# --- uji 2: fixture group tercatat, skrip idempoten ------------------------
grep -q '^keluarga:x:1100:budi,siti$' latihan/etc/group
assert_eq 0 "$?" "fixture group memuat keluarga:budi,siti"
grep -q '^tamu:x:1101:tamu1$' latihan/etc/group
assert_eq 0 "$?" "fixture group memuat tamu"
bash "$PROJ_DIR/deploy/buat-struktur.sh" latihan > /dev/null 2>&1
assert_eq 0 "$?" "buat-struktur.sh idempoten (dijalankan dua kali)"
# --- uji 3: konfigurasi bawaan lolos verifikasi-share.sh -------------------
bash "$PROJ_DIR/deploy/verifikasi-share.sh" "$PROJ_DIR/config" \
> /dev/null 2>&1
assert_eq 0 "$?" "config/ lolos verifikasi-share.sh"
# --- uji 4: konfigurasi rusak tertangkap -----------------------------------
mkdir -p rusak
printf '[global]\n[dokumen]\npath = /srv/share/dokumen\nread only = no\n' \
> rusak/smb.conf
printf '/srv/share/dokumen 192.0.2.0/24(rw,async)\n' > rusak/exports
bash "$PROJ_DIR/deploy/verifikasi-share.sh" rusak > /dev/null 2>&1
if [ "$?" -gt 0 ]; then
PASS=$((PASS + 1))
printf 'OK konfigurasi rusak tertangkap (exit bukan 0)\n'
else
FAIL=$((FAIL + 1))
printf 'GAGAL konfigurasi rusak tertangkap: verifikasi-share.sh keluar 0\n'
fi
# --- uji 5: entri fstab-share memuat nofail dan enam field -----------------
grep -q 'nofail' "$PROJ_DIR/config/fstab-share"
assert_eq 0 "$?" "fstab-share memuat nofail"
jumlah=$(awk '!/^[[:space:]]*#/ && NF { print NF }' \
"$PROJ_DIR/config/fstab-share" | sort -u)
assert_eq 6 "$jumlah" "entri fstab-share berisi enam field"
mnt=$(awk '$2 == "/srv/share" { print $2 }' "$PROJ_DIR/config/fstab-share")
assert_eq "/srv/share" "$mnt" "mountpoint fstab-share /srv/share"
# --- uji 6: setgid diwarisi oleh isi baru folder share ---------------------
mkdir -p latihan/share/dokumen/laporan
touch latihan/share/dokumen/laporan/catatan.txt
assert_eq "$(stat -c '%G' latihan/share/dokumen)" \
"$(stat -c '%G' latihan/share/dokumen/laporan/catatan.txt)" \
"file baru mewarisi group folder share"
mode_sub=$(stat -c '%a' latihan/share/dokumen/laporan)
if [ "$(( 8#$mode_sub & 8#2000 ))" -ne 0 ]; then
PASS=$((PASS + 1))
printf 'OK folder baru mewarisi bit setgid (%s)\n' "$mode_sub"
else
FAIL=$((FAIL + 1))
printf 'GAGAL folder baru mewarisi bit setgid: mode %s tanpa bit 2000\n' \
"$mode_sub"
fi
# --- uji 7: tulis ditolak pada file read-only ------------------------------
printf 'isi awal\n' > latihan/kunci.txt
chmod 444 latihan/kunci.txt
if (echo 'tambahan' >> latihan/kunci.txt) 2> /dev/null; then
FAIL=$((FAIL + 1))
printf 'GAGAL tulis ke file ro ditolak: append justru berhasil\n'
else
PASS=$((PASS + 1))
printf 'OK tulis ke file ro ditolak\n'
fi
assert_eq 'isi awal' "$(cat latihan/kunci.txt)" "isi file ro tidak berubah"
chmod 644 latihan/kunci.txt
echo 'tambahan' >> latihan/kunci.txt
assert_eq 0 "$?" "tulis diizinkan kembali setelah mode rw"
# --- 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
[ "$FAIL" -eq 0 ]