Struktur dari Bagian 1 masih terkunci di dalam server: folder dokumen, media, dan backup sudah berdiri dengan grup dan mode yang benar, tetapi belum satu pun klien yang bisa melihatnya. Bagian ini membuka pintunya lewat dua protokol sekaligus — Samba untuk klien Windows dan ponsel, NFS untuk klien Linux — dengan satu aturan desain: konfigurasi jaringan tidak boleh bertentangan dengan permission di disk. Karena Samba dan NFS tidak terpasang di lingkungan latihan, kita menulis kedua berkas konfigurasinya persis seperti yang akan ditempatkan di server, membedahnya sebaris-sebaris, lalu memvalidasi isinya dengan skrip pemeriksa berbasis grep dan awk.

Seluruh tingkah laku Samba dikendalikan satu berkas: /etc/samba/smb.conf, format INI dengan bagian dalam tanda kurung siku. Anatomi dasarnya sudah dibedah di Chapter Samba ; proyek ini menambahkan tiga hal yang chapter itu belum butuhkan: pembatasan antarmuka jaringan, daftar baca/tulis per grup untuk share yang boleh dilihat tamu, dan mask yang konsisten dengan mode di disk. Berkas berikut ditulis persis seperti yang akan disalin ke /etc/samba/smb.conf — versi lengkapnya tersedia sebagai config/smb.conf pada bagian Berkas Sumber di bawah halaman ini:

bash
mkdir -p config
cat > config/smb.conf <<'SMB'
# smb.conf — file server rumahan: share dokumen, media, dan backup.
#
# Penempatan di server:
#   sudo cp smb.conf /etc/samba/smb.conf
#   sudo testparm                     # validasi sintaks + daftar share
#   sudo systemctl reload smbd        # terapkan tanpa memutus koneksi
#
# Uji cepat dari klien:
#   smbclient -L //server-rumah -U budi

[global]
   workgroup = RUMAH
   server string = Server File Rumah
   server role = standalone server
   log file = /var/log/samba/log.%m
   max log size = 1000
   interfaces = 192.0.2.0/24 127.0.0.1
   bind interfaces only = yes

[dokumen]
   comment = Dokumen kerja dan catatan keluarga
   path = /srv/share/dokumen
   valid users = @keluarga
   read only = no
   browseable = yes
   create mask = 0660
   directory mask = 2770

[media]
   comment = Foto dan video keluarga
   path = /srv/share/media
   valid users = @keluarga @tamu
   read only = no
   read list = @tamu
   write list = @keluarga
   browseable = yes
   create mask = 0664
   directory mask = 2775

[backup]
   comment = Arsip backup laptop
   path = /srv/share/backup
   valid users = budi
   read only = no
   browseable = no
   create mask = 0600
   directory mask = 2700
SMB
cat config/smb.conf

Output yang Dihasilkan:

text
# smb.conf — file server rumahan: share dokumen, media, dan backup.
#
# Penempatan di server:
#   sudo cp smb.conf /etc/samba/smb.conf
#   sudo testparm                     # validasi sintaks + daftar share
#   sudo systemctl reload smbd        # terapkan tanpa memutus koneksi
#
# Uji cepat dari klien:
#   smbclient -L //server-rumah -U budi

[global]
   workgroup = RUMAH
   server string = Server File Rumah
   server role = standalone server
   log file = /var/log/samba/log.%m
   max log size = 1000
   interfaces = 192.0.2.0/24 127.0.0.1
   bind interfaces only = yes

[dokumen]
   comment = Dokumen kerja dan catatan keluarga
   path = /srv/share/dokumen
   valid users = @keluarga
   read only = no
   browseable = yes
   create mask = 0660
   directory mask = 2770

[media]
   comment = Foto dan video keluarga
   path = /srv/share/media
   valid users = @keluarga @tamu
   read only = no
   read list = @tamu
   write list = @keluarga
   browseable = yes
   create mask = 0664
   directory mask = 2775

[backup]
   comment = Arsip backup laptop
   path = /srv/share/backup
   valid users = budi
   read only = no
   browseable = no
   create mask = 0600
   directory mask = 2700

Mari kita bedah directive demi directive yang baru atau berbeda dari chapter dasar:

  1. interfaces = 192.0.2.0/24 127.0.0.1 dan bind interfaces only = yes: smbd hanya mendengarkan di segmen LAN rumah dan loopback — tidak di interface lain sekalipun ada. Server rumahan yang juga terhubung ke jaringan tamu atau VPN tanpa sengaja ikut membuka share di sana.
  2. log file = /var/log/samba/log.%m: satu berkas log per nama klien — %m diganti nama mesinnya. Berkas-berkas inilah bahan audit di Bagian 3.
  3. valid users = @keluarga: tanda @ berarti keanggotaan grup. User Samba di luar grup ditolak bahkan sebelum menyentuh permission disk — share dokumen dengan demikian punya dua pintu: daftar user di sini, lalu mode 2770 di disk.
  4. read list = @tamu dan write list = @keluarga pada [media]: kombinasi inilah yang membuat satu share punya dua tingkat akses. read only = no membuka tulis secara umum, read list menurunkan tamu ke hanya-baca, dan write list memastikan keluarga tetap bisa menulis — permission berlapis yang tidak bisa dinyatakan mode disk saja, karena di disk tamu memang membaca lewat kelas other.
  5. browseable = no pada [backup]: share tidak tampil di daftar penelusuran, tetapi tetap bisa dibuka bila namanya diketahui. Share backup memang bukan urusan klien sehari-hari.
  6. create mask = 0660 dan directory mask = 2770: permission yang diterapkan Samba pada berkas dan folder baru yang dibuat lewat share — angka ini sengaja sama dengan mode disk dari Bagian 1. Perhatikan directory mask = 2770: folder baru yang dibuat klien Windows pun tetap membawa bit setgid, sehingga skema grup tidak perlahan rusak oleh klien yang tidak tahu apa-apa soal Linux.

Menyusun /etc/exports #

Klien Linux tidak membutuhkan bahasa Windows — mereka bicara NFS, dan seluruh konfigurasinya muat dalam /etc/exports: satu baris per direktori, diikuti klien beserta opsinya dalam kurung. Rancangan kita membagi ketiga share dengan tingkat kepercayaan yang berbeda — versi lengkapnya tersedia sebagai config/exports pada bagian Berkas Sumber di bawah halaman ini:

bash
mkdir -p config
cat > config/exports <<'NFS'
# /etc/exports — share NFS untuk klien Linux di jaringan rumah.
#
# Penempatan di server:
#   sudo cp exports /etc/exports
#   sudo exportfs -ra               # segarkan seluruh entri
#   sudo exportfs -v               # tampilkan tabel ekspor aktif
#
# Format: <direktori> <klien>(<opsi>)

/srv/share/dokumen  192.0.2.0/24(rw,sync,no_subtree_check)
/srv/share/media    192.0.2.0/24(ro,sync,no_subtree_check)
/srv/share/backup   192.0.2.10(rw,sync,no_subtree_check)
NFS
cat config/exports

Output yang Dihasilkan:

text
# /etc/exports — share NFS untuk klien Linux di jaringan rumah.
#
# Penempatan di server:
#   sudo cp exports /etc/exports
#   sudo exportfs -ra               # segarkan seluruh entri
#   sudo exportfs -v               # tampilkan tabel ekspor aktif
#
# Format: <direktori> <klien>(<opsi>)

/srv/share/dokumen  192.0.2.0/24(rw,sync,no_subtree_check)
/srv/share/media    192.0.2.0/24(ro,sync,no_subtree_check)
/srv/share/backup   192.0.2.10(rw,sync,no_subtree_check)

Bedah tiap keputusan dan opsinya:

  1. /srv/share/dokumen 192.0.2.0/24(rw,...): seluruh subnet boleh memasang share ini secara baca-tulis — tetapi “rw” di sini hanya izin tingkat protokol. Kernel tetap memeriksa permission di disk: klien yang login sebagai user non-anggota grup keluarga akan menabrak mode 2770 dan gagal menulis. NFS memercayai uid/gid yang diklaim klien, dan pemeriksaan grup inilah yang menjaga pintunya.
  2. /srv/share/media ... (ro,...): media diekspor hanya-baca untuk semua klien NFS — cukup untuk streaming dan menyalin foto ke ponsel. Penambahan isi baru terjadi lewat Samba (atau langsung di server), bukan lewat NFS, sehingga koleksi lama tidak bisa berubah oleh salah ketik di klien Linux mana pun.
  3. /srv/share/backup 192.0.2.10(rw,...): hanya satu klien — 192.0.2.10, laptop-budi — yang boleh memasang share backup. Klien lain bahkan tidak ditawari entri ini. laptop-budi-lah yang mendorong arsip backup ke server, kebalikan dari pola biasa; alih-alih server menarik data, klien tepercaya yang mendorong.
  4. sync: server menuliskan perubahan ke disk sebelum menjawab sukses ke klien — lebih lambat daripada async, tetapi data tidak menguap saat listrik padam mendadak.
  5. no_subtree_check: mematikan pemeriksaan “apakah berkas ini masih di bawah direktori yang diekspor” yang lambat dan sering salah kirim peringatan; hampir semua panduan modern menuliskannya.

Membuka Port di Firewall #

Firewall yang baik tetap pelit meski jaringannya hanya rumah: semuanya ditolak kecuali yang kita izinkan, dan izinnya sebatas subnet LAN. Empat port yang dibutuhkan:

PortProtokolLayananDiperlukan oleh
445TCPsmbd — SMB modernsemua klien Samba
139TCPsmbd — SMB lama atas NetBIOSklien sangat tua
137–138UDPnmbd — penemuan nama NetBIOSopsional
2049TCPnfsd — NFSv4klien Linux

Perintah ufw-nya dijalankan sekali di server — perhatikan bahwa sumbernya dibatasi from 192.0.2.0/24, bukan terbuka untuk Anywhere:

bash
sudo ufw allow from 192.0.2.0/24 to any port 445 proto tcp
sudo ufw allow from 192.0.2.0/24 to any port 139 proto tcp
sudo ufw allow from 192.0.2.0/24 to any port 137:138 proto udp
sudo ufw allow from 192.0.2.0/24 to any port 2049 proto tcp
sudo ufw status numbered

sudo ufw status numbered menampilkan daftar berurut untuk diperiksa ulang. Port 139 serta 137–138 hanya perlu dibuka bila masih ada klien tua yang menemukan share lewat penelusuran jaringan; klien modern cukup 445. NFSv4 memang dirancang dengan satu port 2049 — salah satu alasan kita memakainya, karena NFSv3 menuntut portmapper 111 dan serangkaian port acak yang menyulitkan firewall, sebagaimana dibandingkan di Chapter NFS .

Validasi dengan verifikasi-share.sh #

Di server, sudo testparm memvalidasi sintaks smb.conf dan sudo exportfs -v menampilkan tabel ekspor aktif — keduanya sudah dibahas di chapter masing-masing. Namun keduanya baru bisa berjalan setelah paket terpasang di server. Pemeriksaan berikut sengaja lebih awal dan lebih sempit: sebelum berkas sampai ke server, apakah semua directive yang kita rancang benar-benar ada, dan apakah format exports sah? Skrip verifikasi-share.sh — tersedia sebagai deploy/verifikasi-share.sh pada bagian Berkas Sumber — menjawabnya dengan grep untuk directive literal dan awk untuk format; blok berikut menulis ulang kedua berkas konfigurasi dan skripnya agar mandiri, lalu mengujinya pada konfigurasi lengkap dan pada versi yang sengaja dibolongi:

bash
mkdir -p config
cat > config/smb.conf <<'SMB'
# smb.conf — file server rumahan: share dokumen, media, dan backup.
#
# Penempatan di server:
#   sudo cp smb.conf /etc/samba/smb.conf
#   sudo testparm                     # validasi sintaks + daftar share
#   sudo systemctl reload smbd        # terapkan tanpa memutus koneksi
#
# Uji cepat dari klien:
#   smbclient -L //server-rumah -U budi

[global]
   workgroup = RUMAH
   server string = Server File Rumah
   server role = standalone server
   log file = /var/log/samba/log.%m
   max log size = 1000
   interfaces = 192.0.2.0/24 127.0.0.1
   bind interfaces only = yes

[dokumen]
   comment = Dokumen kerja dan catatan keluarga
   path = /srv/share/dokumen
   valid users = @keluarga
   read only = no
   browseable = yes
   create mask = 0660
   directory mask = 2770

[media]
   comment = Foto dan video keluarga
   path = /srv/share/media
   valid users = @keluarga @tamu
   read only = no
   read list = @tamu
   write list = @keluarga
   browseable = yes
   create mask = 0664
   directory mask = 2775

[backup]
   comment = Arsip backup laptop
   path = /srv/share/backup
   valid users = budi
   read only = no
   browseable = no
   create mask = 0600
   directory mask = 2700
SMB
cat > config/exports <<'NFS'
# /etc/exports — share NFS untuk klien Linux di jaringan rumah.
#
# Penempatan di server:
#   sudo cp exports /etc/exports
#   sudo exportfs -ra               # segarkan seluruh entri
#   sudo exportfs -v               # tampilkan tabel ekspor aktif
#
# Format: <direktori> <klien>(<opsi>)

/srv/share/dokumen  192.0.2.0/24(rw,sync,no_subtree_check)
/srv/share/media    192.0.2.0/24(ro,sync,no_subtree_check)
/srv/share/backup   192.0.2.10(rw,sync,no_subtree_check)
NFS
cat > verifikasi-share.sh <<'BASH'
#!/usr/bin/env bash
# verifikasi-share.sh — periksa directive wajib smb.conf dan format exports.
# 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"
BASH
echo "== konfigurasi lengkap =="
bash verifikasi-share.sh config; echo "exit=$?"
echo "== konfigurasi rusak =="
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/srv/share/backup  192.0.2.10(rw,sync,no_subtree_check,no_root_squash)\n' \
    > rusak/exports
bash verifikasi-share.sh rusak; echo "exit=$?"

Output yang Dihasilkan:

text
== konfigurasi lengkap ==
OK     smb.conf: [global]
OK     smb.conf: [dokumen]
OK     smb.conf: [media]
OK     smb.conf: [backup]
OK     smb.conf: path = /srv/share/dokumen
OK     smb.conf: path = /srv/share/media
OK     smb.conf: path = /srv/share/backup
OK     smb.conf: valid users = @keluarga
OK     smb.conf: read only = no
OK     smb.conf: create mask = 0660
OK     smb.conf: directory mask = 2770
OK     smb.conf: browseable = no
OK     smb.conf: bind interfaces only = yes
OK     exports: semua entri memuat opsi klien
OK     exports: semua entri memuat sync + no_subtree_check
OK     exports: root_squash tetap aktif
OK     exports: dokumen dibagikan rw
OK     exports: media dibagikan ro
OK     exports: backup terbatas ke 192.0.2.10
exit=0
== konfigurasi rusak ==
OK     smb.conf: [global]
OK     smb.conf: [dokumen]
HILANG smb.conf: [media]
HILANG smb.conf: [backup]
OK     smb.conf: path = /srv/share/dokumen
HILANG smb.conf: path = /srv/share/media
HILANG smb.conf: path = /srv/share/backup
HILANG smb.conf: valid users = @keluarga
OK     smb.conf: read only = no
HILANG smb.conf: create mask = 0660
HILANG smb.conf: directory mask = 2770
HILANG smb.conf: browseable = no
HILANG smb.conf: bind interfaces only = yes
OK     exports: semua entri memuat opsi klien
HILANG exports: ada entri tanpa sync / no_subtree_check
HILANG exports: no_root_squash terdeteksi
OK     exports: dokumen dibagikan rw
HILANG exports: media bukan ro
OK     exports: backup terbatas ke 192.0.2.10
exit=12

Empat keputusan di dalamnya:

  1. grep -qF -- "$pola": opsi -F mencari teks literal, bukan regex, sehingga pola seperti [media] aman dari tafsiran karakter kurung. Daftar pola menjadi data murni — menambah directive wajib berarti menambah satu baris, bukan menyunting logika.
  2. Pola awk ! /^[[:space:]]*#/ && NF: hanya baris aktif yang dihitung; c + 0 di blok END memastikan hasil berupa angka meski tak ada yang cocok — trik yang sama dengan audit format di Chapter NFS .
  3. sub(/\(.*/, "", k): nama klien dipenggal dari opsinya (192.0.2.10(rw,...) menjadi 192.0.2.10) sebelum dibandingkan — pembandingan string setelahnya menjadi ketat, bukan sekadar “mengandung”.
  4. exit "$gagal": jumlah pelanggaran menjadi kode keluar, sehingga test suite cukup memeriksa angka itu. Konfigurasi rusak di atas tertangkap dua belas kali sekaligus: sembilan directive smb.conf hilang, satu entri tanpa sync, no_root_squash menyala, dan media tidak diekspor ro — testparm mungkin masih meloloskan sintaksnya karena formatnya sah, tetapi desain kita jelas rusak. Daftar lengkap parameter ada di smb.conf(5) dan exports(5) ; panduan tingkat server lengkap tersedia di dokumentasi Samba .

Kedua protokol kini terpasang di atas kertas: smb.conf memetakan tiga share dengan user dan mask yang sejajar dengan mode disk, exports membatasi NFS per klien, dan firewall membuka empat port untuk subnet rumah. Yang belum kita kerjakan adalah lapisan penghuninya — user mana masuk grup mana, bagaimana setgid menjaga grup tetap terwarisi, dan bagaimana membuktikan tamu memang ditolak. Itu seluruh isi Bagian 3.

Berkas Sumber

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

deploy/buat-struktur.sh

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