Layanan cloud memang praktis, tetapi tagihannya datang setiap bulan, foto keluarga tersimpan di server milik orang lain, dan menyalin ratusan gigabyte lewat internet rumahan memakan waktu berhari-hari. Sementara itu, jaringan lokal di rumah dan kantor kecil sudah berjalan gigabit — menyalin berkas yang sama lewat LAN hanya soal menit. Proyek ini membangun alternatifnya: file server lokal di atas satu mesin Linux, dengan struktur penyimpanan yang direncanakan, share Samba untuk klien Windows dan ponsel, NFS untuk klien Linux, serta permission per grup sehingga anggota keluarga bisa menulis, tamu hanya membaca, dan backup tidak tersentuh siapa pun kecuali admin.
Server yang kita bangun menyediakan tiga hal sekaligus:
- struktur penyimpanan terencana — tiga share terpisah per fungsi (
dokumen,media,backup) di atas disk data sendiri yang dipasang lewat entrifstabdengan opsinofail, - berbagi file dua protokol — Samba untuk klien Windows dan ponsel, NFS untuk klien Linux, masing-masing dengan batasan aksesnya,
- permission per grup — grup
keluargamembaca-menulis, gruptamuhanya membaca share media, dan share backup hanya untuk admin — semuanya ditegakkan bit setgid pada folder share sehingga berkas baru selalu jatuh ke grup yang benar.
Tampilan Akhir Server #
Sesi berikut menunjukkan keadaan akhir yang bisa dibuktikan di lingkungan latihan. Skrip buat-struktur.sh membangun ketiga share beserta permission-nya, lalu test suite memeriksa struktur, konfigurasi, pewarisan setgid, dan penolakan tulis pada berkas read-only:
$ bash deploy/buat-struktur.sh latihan
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
$ stat -c '%a %n' latihan/share/*
2770 latihan/share/backup
2770 latihan/share/dokumen
2775 latihan/share/media
$ bash tests/run-tests.sh | tail -n 3
OK isi file ro tidak berubah
OK tulis diizinkan kembali setelah mode rw
PASSED: 20/20 test lulus
Mode 2770 pada dua folder pertama bukan typo — digit 2 di depan adalah bit setgid yang membuat semua berkas baru di dalamnya mewarisi grup folder, fondasi dari seluruh skema permission proyek ini.
Arsitektur Akhir #
Seluruh keputusan di atas bertemu dalam satu gambar berikut:
Klien di jaringan rumah — LAN 192.0.2.0/24
Windows laptop ─┐ ┌─ ponsel Android ─ Samba, TCP 445
│ │
▼ ▼
[ smbd ] port 445 — share Samba untuk dokumen, media, backup
│ valid users = @keluarga @tamu (per share)
│ media: write list @keluarga, read list @tamu
│
laptop-budi ──► [ nfsd ] port 2049 — NFSv4 untuk klien Linux
│ dokumen: rw media: ro backup: 192.0.2.10 saja
▼
/srv/share/ ← disk data terpisah, fstab: defaults,nofail
├── dokumen/ grup keluarga mode 2770 rw anggota, tamu ditolak
├── media/ grup keluarga mode 2775 rw anggota, tamu baca
└── backup/ grup backup mode 2770 hanya admin (budi)Setiap baris di gambar itu dibangun dan diperiksa satu per satu: disk data dan struktur folder di Bagian 1, kedua protokol berbagi di Bagian 2, dan lapisan user-grup-permission beserta auditnya di Bagian 3.
Tujuan Pembelajaran #
Setelah menyelesaikan proyek ini, kita mampu:
- merancang struktur share per fungsi beserta pemetaan grup dan mode permission-nya sebelum mengetik satu perintah pun,
- menyiapkan disk data terpisah dengan entri
fstabber-nofailyang aman saat boot, - menulis
smb.conflengkap denganvalid users,read list/write list, dan mask yang konsisten dengan permission di disk, - menyusun
/etc/exportsdengan opsi keamanan NFS yang tepat:rw/roper klien,sync,no_subtree_check, danroot_squashyang tetap aktif, - menerapkan pola grup bersama:
groupadd,usermod -aG, bit setgid2770, dan pengecualian per user lewat ACL, - memvalidasi konfigurasi dan permission lewat skrip pemeriksa serta test suite bash yang bisa dijalankan ulang.
Prasyarat #
Proyek ini merangkai materi dari beberapa section sekaligus. Chapter yang paling sering dirujuk:
- Samba
— anatomi
smb.conf, user Samba dua lapis, dan pemasangan dari klien, - NFS
— format
/etc/exports, opsi per klien, dan jebakanroot_squash, - File Permissions — kelas owner/group/other dan mode oktal,
- fstab
— field entri mount permanen, UUID, dan opsi
nofail.
Tahapan Pengerjaan #
Proyek dikerjakan dalam tiga bagian berurutan, dan tiap bagian diakhiri dengan sesuatu yang bisa diperiksa:
- Bagian 1: Konfigurasi Penyimpanan — merancang tabel share, menyiapkan disk data dengan entri
fstab, lalu membangun struktur/srv/sharebeserta grup dan mode-nya lewat skrip. - Bagian 2: Berbagi File — menulis
smb.confuntuk tiga share dan/etc/exportsuntuk klien Linux, membuka port yang diperlukan, lalu memvalidasi semuanya denganverifikasi-share.sh. - Bagian 3: Akses dan Permissions — mengelola grup dan user, membuktikan setgid mewariskan grup, menyusun pengecualian ACL, menguji akses dari klien, dan mengaudit siapa mengakses apa.
Mulailah dari Bagian 1 karena tabel share dan mode-nya menjadi acuan langsung bagi konfigurasi Samba, ekspor NFS, dan test suite pada dua bagian berikutnya. Berkas proyek (config/ berisi smb.conf, exports, dan fstab-share; deploy/ berisi kedua skrip; tests/run-tests.sh; serta docs/README.txt) tersedia pada bagian Berkas Sumber di bawah halaman setiap bagian, sehingga hasil pekerjaan bisa dibandingkan dengan versi referensi kapan saja.
Siapkan struktur direktori, partisi data, dan permission dasar file server.
Buka → # Bagian 2: Berbagi FileKonfigurasikan share untuk klien berbeda beserta opsi keamanannya.
Buka → # Bagian 3: Akses dan PermissionsAtur user, group, dan permission agar tiap share hanya diakses yang berhak.
Buka →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 ]