Aplikasi ujian berbasis komputer untuk sekolah dan madrasah. Dibangun di atas CodeIgniter 4 dan berjalan sepenuhnya di dalam Docker. Dua keputusan membedakannya dari CBT pada umumnya: komunikasi real-time ditangani daemon WebSocket terpisah sehingga ratusan peserta serentak tidak menghabiskan worker PHP-FPM, dan halaman soal dapat dibekukan menjadi berkas statis sehingga tahan lonjakan akses di menit-menit pertama ujian.
- Bank soal enam tipe — pilihan ganda satu jawaban, pilihan ganda kompleks, esai atau teks singkat, menjodohkan, benar/salah, dan mengurutkan.
- Import dari Word — soal ditulis di
.docxdengan penomoran biasa, tanpa kode format khusus sepertiQ:atauRIGHT:. Gambar yang tertanam di dokumen ikut terbawa. - Mode ujian statis — generator yang membekukan halaman soal menjadi HTML, menghilangkan pekerjaan PHP per permintaan pada saat beban puncak.
- Live proctoring — pengawas melihat peserta yang sedang mengerjakan dan dapat memberi tambahan waktu, mengeluarkan, atau memblokir sesi secara langsung tanpa memuat ulang halaman.
- EXAMBRO — aplikasi Android pengunci layar untuk skema BYOD, dengan sirine saat peserta memaksa keluar, overlay guard, blokir clipboard, pemaksaan home launcher, dan deteksi perangkat ter-root atau emulator.
- Deteksi kecurangan di browser — peringatan otomatis saat peserta keluar dari fullscreen atau berpindah tab, dengan ambang pelanggaran yang diatur administrator.
- Antrean slot dan rem login — batas peserta serentak dengan halaman antrean, serta throttling login per-IP yang dirancang tidak menghukum sekolah di balik CGNAT.
- Analisis butir soal — tingkat kesukaran, daya beda, dan rincian pengecoh per butir, dapat diekspor ke CSV.
- Audit trail dan pelaporan — riwayat aktivitas dengan daftar pengguna online
real-time dan export laporan ke
.xls.
Halaman mulai ujian di desktop.
Pengerjaan soal di ponsel. Tata letak menyesuaikan layar kecil, sehingga skema BYOD dapat berjalan tanpa aplikasi terpisah.
- Docker dan Docker Compose V2
- Host Linux
- Akses root — installer mengatur kepemilikan direktori ke GID 33 (
www-data)
git clone https://github.com/rozendev/CBT-MF.git
cd CBT-MF
sudo bash scripts/cbt.sh installInstaller bersifat interaktif dan menanyakan nama database, username dan password database, prefix nama container, token Cloudflare Tunnel (boleh dikosongkan), base URL aplikasi, password Redis, serta username dan password akun administrator pertama. Tidak ada kredensial bawaan: password admin adalah yang Anda ketik sendiri.
Setelah pertanyaan selesai, installer menulis .env dan src/.env, membangun
dan menyalakan seluruh container, membetulkan permission direktori, menjalankan
composer install, menjalankan migrasi database, dan membuat akun administrator.
Tidak ada langkah manual tambahan.
| Layanan | Alamat | Keterangan |
|---|---|---|
| Aplikasi (Nginx) | http://localhost:8080 |
Satu-satunya port yang terekspos ke jaringan |
| WebSocket | 127.0.0.1:8060 |
Terikat ke localhost; diproksi Nginx lewat /ws/ |
| MariaDB | — | Hanya di dalam jaringan Docker |
| Redis | — | Hanya di dalam jaringan Docker |
Untuk membuka MariaDB atau Redis, gunakan scripts/cbt.sh db shell dan
scripts/cbt.sh redis shell; keduanya tidak dipublikasikan ke host.
scripts/cbt.sh adalah satu-satunya pintu operasional. Dijalankan tanpa
argumen, ia membuka menu interaktif:
sudo bash scripts/cbt.shAtau langsung sebagai subperintah:
sudo bash scripts/cbt.sh <grup> <perintah>| Grup | Perintah |
|---|---|
docker |
up, down, restart, logs, status |
app |
shell, php, composer |
db |
shell, root, export, import, reset-password |
redis |
shell, flush |
bundle |
build, status |
data |
images, optimize, cache-clear, finalize, prune-kiosk |
migrate |
up, status, rollback |
tune |
show, set |
Perintah tingkat atas tanpa grup: backup, log-rotate, reset-install,
test-k6, install, help.
Perintah yang merusak data (db import, redis flush, data optimize,
migrate rollback, reset-install) menuntut Anda mengetik ulang nama
perintahnya sebelum dijalankan.
Beberapa perintah yang sering dipakai:
sudo bash scripts/cbt.sh docker status # periksa kondisi seluruh container
sudo bash scripts/cbt.sh docker logs # ikuti log semua layanan
sudo bash scripts/cbt.sh backup # backup database dan Redis
sudo bash scripts/cbt.sh db reset-password # setel ulang password admin
sudo bash scripts/cbt.sh data finalize # tutup attempt yang lewat batas waktuSatu hal yang mudah terlewat: setelah mengubah UI ujian, bundle kiosk wajib dibangun ulang.
sudo bash scripts/cbt.sh bundle buildTanpa langkah ini, perangkat yang memakai EXAMBRO tetap menerima artefak lama
dan tidak ada pesan galat apa pun yang memberi tahu Anda. Gunakan
bundle status untuk membandingkan versi bundle lokal, server, dan zip publik.
flowchart LR
B["Browser peserta"] --> N["Nginx"]
E["EXAMBRO Android"] --> N
N -->|"HTTP"| P["PHP-FPM<br/>CodeIgniter 4"]
N -->|"/ws/"| W["Daemon WebSocket<br/>ReactPHP dan Ratchet"]
P --> R[("Redis")]
W --> R
P --> M[("MariaDB")]
C["Cron"] --> M
C --> R
Tiga keputusan yang menjelaskan bentuk di atas:
Daemon WebSocket terpisah. Komunikasi server-ke-klien tidak lewat PHP-FPM. Daemon single-thread berbasis ReactPHP mendengarkan Redis Pub/Sub dan menyiarkan perintah pengawas ke peserta. Satu proses kecil menggantikan ratusan worker yang tertahan, sehingga jumlah peserta serentak tidak lagi dibatasi jumlah worker. Konsekuensinya, tidak boleh ada operasi sinkronus yang memblokir event loop di dalam daemon — termasuk koneksi PDO ke MariaDB.
Autosave ke Redis, flush ke MariaDB. Jawaban peserta ditulis ke Redis selama ujian berlangsung dan baru dipindahkan ke MariaDB secara massal di akhir. Ujian karenanya tidak menghasilkan satu penulisan database per jawaban.
Mode ujian statis. Halaman soal dibekukan menjadi HTML sehingga pembacaannya tidak memerlukan pekerjaan PHP maupun kueri database per permintaan.
Tujuh service di docker-compose.yml:
| Service | Peran |
|---|---|
nginx |
Reverse proxy dan penyaji berkas statis |
php |
PHP-FPM menjalankan aplikasi CodeIgniter 4 |
websocket |
Daemon spark websocket:serve |
cron |
Menutup attempt kedaluwarsa, probe dependensi, membersihkan kunci kiosk basi |
cloudflared |
Cloudflare Tunnel, aktif bila CF_TUNNEL_TOKEN diisi |
mariadb |
Basis data |
redis |
Sesi, cache, autosave, dan Pub/Sub |
| Direktori | Isi |
|---|---|
src/ |
Aplikasi CodeIgniter 4 |
docker/ |
Dockerfile dan konfigurasi Nginx, PHP-FPM, MariaDB |
scripts/ |
cbt.sh dan utilitas pendukung |
cbt-kiosk-app/ |
Sumber aplikasi Android EXAMBRO |
docs/ |
Panduan Cloudflare, Nginx, dan rancangan fitur |
docker-compose.yml |
Orkestrasi container |
Dockerfile.railway, railway.json, docker/railway/ |
Berkas pendukung deploy ke Railway |
Selain pemasangan Docker di atas, tersedia jalur deploy ke Railway untuk lingkungan uji yang bisa diakses dari mana saja tanpa menyiapkan VPS. Panduannya di RAILWAY.md.
Repositori ini publik, jadi apa pun yang tertulis di dalamnya diketahui semua orang. Tidak ada satu pun rahasia bawaan yang bisa dipakai — semuanya harus Anda isi sendiri.
Isi INTRUDER_TOKEN di .env, dan sulihkan ke halaman honeypot.
scripts/cbt.sh install tidak menyentuh variabel ini, jadi setelah instalasi
nilainya masih kosong. Selama kosong, endpoint laporan penyusup menolak semua
permintaan dengan status 503 dan halaman honeypot 403.html serta 404.html
tidak mencatat apa pun. Sisi klien dan sisi server harus memakai nilai yang
sama, sehingga token yang sudah Anda buat wajib disulihkan juga ke kedua
halaman itu.
openssl rand -hex 32Isikan hasilnya ke INTRUDER_TOKEN di .env, lalu ganti nilai TOKEN di
docker/nginx/html/errors/403.html dan docker/nginx/html/errors/404.html
dengan token yang sama.
Isi DB_PASSWORD dan MYSQL_ROOT_PASSWORD. .env.example sengaja memuat
penanda GANTI_..., bukan sandi yang bisa dipakai. Bila Anda memasang lewat
scripts/cbt.sh install, keduanya sudah ditanyakan saat instalasi.
Isi KIOSK_APP_SECRET. Opsional, tetapi tanpa itu endpoint verifikasi
keluar kiosk hanya dilindungi rate-limit per-IP.
Setel CI_ENVIRONMENT = production di src/.env. Nilai ini menonaktifkan
tampilan galat yang membocorkan jalur berkas dan kueri.
Batasi port 8080. Bila Anda memakai Cloudflare Tunnel atau reverse proxy
terpisah, ubah pemetaan port Nginx di docker-compose.yml menjadi
127.0.0.1:8080:80 agar aplikasi tidak dapat dijangkau langsung dari internet.
Tiga kasus yang paling sering menimpa pemasangan baru. Kasus lain ada di Troubleshooting.md.
Gejala: peringatan "Reconnecting WebSocket..." muncul terus menerus di halaman ujian.
- Periksa container WebSocket berjalan:
sudo bash scripts/cbt.sh docker status. - Bila statusnya restarting atau exited, baca lognya:
sudo bash scripts/cbt.sh docker logs. - Muat ulang konfigurasi proxy:
sudo bash scripts/cbt.sh docker restart. - Bila hanya terjadi pada halaman mode statis, hapus halaman statis itu
dari menu admin lalu buat ulang. Halaman lama mungkin tidak membawa parameter
user_idpada URL WebSocket-nya.
Gejala: pesan tidak dapat menulis berkas HTML, atau
Could not move file "..." to "/var/www/html/public/uploads/".
Direktori src/writable/, src/public/static/, dan src/public/uploads/ harus
dapat ditulis oleh PHP-FPM. Jangan memakai chmod 777; atur kepemilikan ke
grup web server dan beri izin 775.
mkdir -p src/writable src/public/static src/public/uploads
sudo chown -R :33 src/writable src/public/static src/public/uploads
sudo chmod -R 775 src/writable src/public/static src/public/uploadsGejala: Unable to connect to the database ... Access denied for user, padahal
password yang dimasukkan sudah benar.
MariaDB hanya menetapkan username dan password satu kali, yaitu saat volume dibuat pertama kali. Bila Anda pernah memasang dengan kredensial lama, volume lama tetap dipakai. Volume itu harus dihapus agar MariaDB terinisialisasi ulang.
Perintah berikut menghapus seluruh data di database.
docker compose down -v
sudo bash scripts/cbt.sh installLaporan bug dan usulan fitur dibuka lewat GitHub Issues.
Untuk perubahan kode, buat branch dari main dan ajukan pull request. Jalankan
pengujian di dalam container PHP:
sudo bash scripts/cbt.sh app shell
composer testSuite Throttling memakai bootstrap terpisah karena membutuhkan framework yang
dimuat penuh, dan sengaja tidak dimasukkan ke composer test. Jalankan
terpisah:
vendor/bin/phpunit -c phpunit.throttling.xml.distBila perubahan Anda menyentuh UI ujian, jalankan scripts/cbt.sh bundle build
dan sertakan hasilnya, karena perangkat kiosk memuat bundle tersebut, bukan
berkas sumbernya.
Pertanyaan dan laporan masalah lewat
GitHub Issues. Sertakan keluaran
scripts/cbt.sh docker status dan potongan log yang relevan; keduanya
mempersingkat penelusuran secara berarti.
Aktif dikembangkan. Versi aplikasi saat ini 1.30.
Perangkat lunak ini dilisensikan di bawah GNU Affero General Public License
v3.0 atau versi setelahnya (AGPL-3.0-or-later). Teks lengkapnya ada di berkas
LICENSE.
Yang perlu diketahui sebelum memakainya:
- Anda bebas memakai, mempelajari, mengubah, dan menyebarkan aplikasi ini.
- Setiap turunan yang Anda sebarkan harus memakai lisensi yang sama.
- Pasal 13 AGPL — inilah yang membedakannya dari GPL biasa: jika Anda mengubah aplikasi ini lalu menjalankannya sebagai layanan yang diakses lewat jaringan, Anda wajib menawarkan kode sumber versi Anda kepada para penggunanya, meskipun Anda tidak pernah membagikan berkasnya. Karena CBT ini memang dipakai lewat jaringan, pasal itu berlaku pada pemakaian yang paling lazim.
Menjalankan aplikasi ini apa adanya untuk ujian di sekolah Anda tidak mewajibkan apa pun — kewajiban pasal 13 baru muncul kalau Anda mengubah kodenya dan menyajikan hasil ubahannya kepada pengguna lain lewat jaringan.
Ringkasan ini ditulis untuk memudahkan, bukan sebagai nasihat hukum; yang
mengikat adalah teks di LICENSE.
Seluruh dependensi Composer berlisensi permisif dan sejalan dengan AGPL-3.0:
34 paket MIT, 27 paket BSD-3-Clause, dan satu paket LGPL-3.0-only
(phpoffice/phpword). Daftar terkini bisa dilihat dengan:
sudo bash scripts/cbt.sh app composer licenses





