No description
  • TypeScript 93.7%
  • HTML 5.1%
  • JavaScript 1%
  • CSS 0.1%
Find a file
cola 987d32b80a fix: urutan kelas pada widget siswa per kelas
Sorting sebelumnya hanya parseInt() sehingga kelas dengan suffix
(1A, 1B, 2A, dst) tidak stabil urutannya. Sekarang bandingkan
angka utama dulu, lalu suffix secara alfabet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-12 12:56:06 +07:00
.agents/rules feat: implement automated BMI, Z-score, and nutrition status calculations for health records 2026-06-24 13:06:33 +07:00
.claude feat: add mutasi list functionality with API integration and UI support 2026-03-31 20:15:24 +07:00
.stfolder feat: add status and tujuan columns to surat table and update PWA assets 2026-04-17 05:47:26 +07:00
dist merge: backup dan pemulihan data, dibuat per sekolah 2026-09-11 19:00:24 +07:00
docs/superpowers docs: add frontend design guidelines to ringkasan bulanan plan 2026-03-31 19:39:57 +07:00
drizzle feat improve incoming transfer workflow 2026-08-27 05:55:50 +07:00
public refactor: standardize date formatting across the application using a centralized utility function 2026-03-31 14:11:24 +07:00
scripts fix: halaman login menawarkan pilihan sekolah lagi 2026-09-10 14:36:59 +07:00
src fix: urutan kelas pada widget siswa per kelas 2026-09-12 12:56:06 +07:00
.dockerignore fix: sertakan scripts/ dalam Docker build context 2026-09-11 19:31:09 +07:00
.env.example merge: backup dan pemulihan data, dibuat per sekolah 2026-09-11 19:00:24 +07:00
.gitignore chore: globally clean up and ignore syncthing conflict files 2026-07-01 19:26:10 +07:00
components.json feat: Introduce a new popover component, update application logic, and refresh project dependencies. 2026-02-07 23:24:58 +07:00
docker-compose.yml feat: siapkan SQLite per sekolah untuk ratusan sekolah 2026-09-12 06:17:28 +07:00
DOCKER_DEPLOY.md fix: perjalanan OAuth Google mengikuti domain sekolah 2026-09-11 19:08:55 +07:00
Dockerfile feat: siapkan SQLite per sekolah untuk ratusan sekolah 2026-09-12 06:17:28 +07:00
drizzle.config.ts feat: inti multi-sekolah dengan satu database per sekolah 2026-09-09 19:49:01 +07:00
extract.cjs feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
extract.js feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
extract_guru.js feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
extract_guru.py feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
g7kaih-tracker.jsx feat: implement G7KAIH habit tracker module and integrate into student monitoring dashboard 2026-04-19 08:04:51 +07:00
grid.html feat: implement teacher attendance module and add password management to settings page 2026-03-31 07:07:58 +07:00
implementation_plan.md feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
index.html chore: rename application from SITONG to SITONK across configuration, documentation, and UI components 2026-04-22 19:24:12 +07:00
KEPALA MASCOT.png feat: Introduce mascot image and update application icons and branding. 2026-03-27 12:30:57 +07:00
Laporan Bulanan November 2025.pdf feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
MASCOT.png feat: Introduce mascot image and update application icons and branding. 2026-03-27 12:30:57 +07:00
package-lock.json feat: backup dan pemulihan data lewat halaman Pengaturan 2026-09-11 03:59:54 +07:00
package.json merge: backup dan pemulihan data, dibuat per sekolah 2026-09-11 19:00:24 +07:00
parsed_guru.txt feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
Profil Guru.pdf feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
README.md feat: siapkan SQLite per sekolah untuk ratusan sekolah 2026-09-12 06:17:28 +07:00
refactor.cjs feat: add public student data update page and corresponding server routes 2026-06-26 06:28:22 +07:00
replace_url.cjs refactor: replace S3-specific storage logic with a flexible, unified storage abstraction and move archival services to dedicated modules 2026-06-22 00:42:51 +07:00
seed_sksm.ts feat: implement calon siswa pindahan management module with support for specific registration types and data conversion 2026-05-09 09:44:00 +07:00
task.md feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-absensi.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-cookie.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-final.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-full-headers.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-rawtoken.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test-useragent.html feat: add testing scaffolding and sample data for SIA integration analysis 2026-07-01 19:28:38 +07:00
test2.html feat: implement teacher attendance module and add password management to settings page 2026-03-31 07:07:58 +07:00
test3.html feat: implement teacher attendance module and add password management to settings page 2026-03-31 07:07:58 +07:00
test_js.ts feat: implement calon siswa pindahan management module with support for specific registration types and data conversion 2026-05-09 09:44:00 +07:00
test_pdf.ts feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
test_regex.js feat: Implement guru data management, including extraction, database migration, and UI for listing and viewing details. 2026-03-08 17:54:10 +07:00
tsconfig.json feat: implement OAuth2 for Google Drive, simplify jenis surat settings 2026-02-07 22:48:17 +07:00
tsconfig.tsbuildinfo merge: backup dan pemulihan data, dibuat per sekolah 2026-09-11 19:00:24 +07:00
Tutorial_Siswa_SITONG.md chore: rename application from SITONG to SITONK across configuration, documentation, and UI components 2026-04-22 19:24:12 +07:00
vite.config.ts feat: add paperless picker dialog component and update build configuration 2026-05-15 11:20:18 +07:00
walkthrough.md feat: implement dynamic custom fields and rich global data support for outgoing letters generation 2026-05-27 13:29:18 +07:00

Sistem Informasi Akademik (SIA) / SITONK

Aplikasi Sistem Informasi Akademik dengan fitur manajemen siswa, absensi, rapor, persuratan, perpustakaan digital, dan integrasi penyimpanan cloud.

Satu pemasangan dapat melayani banyak sekolah di bawah satu dinas. Setiap sekolah memiliki database dan folder berkasnya sendiri, memilih sendiri modul yang dipakai, dan tidak dapat melihat data sekolah lain.

Arsitektur Singkat

  • Backend: Node.js (menggunakan Hono framework + @hono/node-server)
  • Database: SQLite (better-sqlite3) dengan Drizzle ORM (drizzle-orm)
  • Frontend: React + Vite + React Router
  • Styling: Tailwind CSS v4 + komponen UI berbasis shadcn/ui
  • Multi-sekolah: Registry pusat (data/registry.db) berisi daftar sekolah dan akun dinas; satu file SQLite per sekolah
  • Auth: Login lokal (email + password, bcrypt) sebagai bawaan; Casdoor OIDC dan Google OAuth opsional
  • Arsip & Storage: Arsip dokumen internal (bawaan) dengan penyimpanan file lokal; opsional S3-compatible, Google Drive, atau server arsip eksternal
  • Ekspor/Impor: Parsing file Excel (xlsx), OCR/PDF (pdf-parse, html2pdf.js)

Multi-Sekolah dan Portal Dinas

Tata letak data

data/
  registry.db                       daftar sekolah + akun dinas
  sia.db  uploads/  laporan/        sekolah pertama (tetap di tempatnya)
  sekolah/<slug>/
    sia.db  uploads/  laporan/      setiap sekolah berikutnya

Lokasi dasarnya diatur lewat SIA_DATA_DIR (bawaan ./data).

Database per sekolah

Setiap sekolah adalah satu berkas SQLite dengan satu koneksi yang dibuka saat pertama dipakai dan dipertahankan selama proses hidup. Saat dibuka, koneksi diatur ke journal_mode = WAL, busy_timeout = 5000, dan synchronous = NORMAL: pembaca tidak tertahan oleh penulis, penulis kedua menunggu alih-alih gagal dengan SQLITE_BUSY, dan setiap commit hemat satu fsync. WAL menghasilkan berkas pendamping sia.db-wal dan sia.db-shm di samping database selama koneksi terbuka; keduanya hilang saat koneksi ditutup dan tidak perlu disalin. Karena itu direktori data harus berada di sistem berkas lokal, bukan NFS atau SMB.

Skema disamakan setiap kali database dibuka: migrasi otomatis, pembuatan tabel, indeks di src/server/db/indexes.ts, lalu pembandingan dengan skema Drizzle di src/server/db/verify-schema.ts. Bila ada migrasi yang gagal atau ada tabel atau kolom yang tidak ada, sekolah itu ditolak, bukan dilayani setengah jadi: koneksinya ditutup, alasannya dicatat di log dan ditampilkan di portal dinas dengan lencana Bermasalah, dan setiap permintaan ke sekolah itu dijawab 503 dengan kode SEKOLAH_SKEMA_GAGAL. Sekolah lain tidak terpengaruh. Setelah operator membetulkan berkasnya, menonaktifkan lalu mengaktifkan kembali sekolah dari portal dinas (atau me-restart server) mencoba ulang migrasinya.

Saat boot semua sekolah aktif dibuka sekali agar migrasi berjalan di awal. Setelah itu koneksi yang tidak disentuh selama SIA_TUTUP_MENGANGGUR_MENIT (bawaan 30, 0 mematikan) ditutup lagi dan dibuka kembali saat permintaan berikutnya datang; skema yang sudah diverifikasi di proses ini tidak diperiksa ulang, jadi pembukaan ulang hanya beberapa milidetik. Koneksi tidak pernah ditutup selama ada permintaan yang sedang dilayani atau pekerjaan backup dan pemulihan yang memegangnya. Dengan begitu pemasangan ratusan sekolah hanya memakai memori dan file descriptor untuk sekolah yang aktif hari itu.

Angka di portal dinas (jumlah siswa, guru, pengguna) tidak dihitung dengan membuka setiap database saat halaman dibuka, melainkan dibaca dari tabel sekolah_stats di registry. Tabel itu diisi setiap kali koneksi sekolah dibuka, setelah sekolah baru didaftarkan, tiap malam pukul 02.30 untuk semua sekolah aktif, dan lewat tombol Hitung ulang sekarang di portal. Hanya hasil COUNT yang keluar dari database sekolah.

Peran

Peran Wewenang
dinas Mendaftarkan sekolah, mengubah profil dan domain, mengaktifkan atau menonaktifkan sekolah, mengelola akun administrator sekolah termasuk reset kata sandi, serta melihat rekap angka siswa dan guru. Tidak dapat masuk ke aplikasi sekolah dan tidak melihat data individu.
admin Seluruh fungsi di dalam satu sekolah, termasuk memilih modul yang dipakai
guru Data kesiswaan dan akademik sesuai kelas yang diampu
siswa Data dirinya sendiri

Portal dinas berada di /dinas dengan sesi terpisah dari aplikasi sekolah.

Menentukan sekolah pada sebuah permintaan

Urutan yang dipakai server:

  1. Domain permintaan cocok dengan domain yang didaftarkan untuk sekolah
  2. Header X-Sekolah-Id (dipakai internal saat mencetak PDF)
  3. Cookie sekolah_id, yaitu pilihan pengguna di halaman login
  4. Bila hanya ada satu sekolah aktif, sekolah itu langsung dipakai

Karena itu sekolah dengan domain sendiri langsung dikenali, sementara satu alamat bersama menampilkan pemilih sekolah di halaman login. Di belakang reverse proxy, teruskan X-Forwarded-Host dan X-Forwarded-Proto.

Bentuk pemasangan yang dipakai: satu domain portal untuk semua orang, dan domain sendiri ditambahkan belakangan untuk sekolah yang memilikinya. Domain sekolah didaftarkan dari portal dinas, dan sejak itu permintaan ke domain itu langsung masuk ke sekolah tersebut tanpa pemilih — cookie pilihan sekolah yang tertinggal dari kunjungan sebelumnya pun kalah oleh domain. Sesi tetap terpisah per sekolah: sesi yang dibuat di satu sekolah ditolak di sekolah lain. Selama hanya ada satu sekolah aktif, domain portal langsung mengarah ke sekolah itu dan pemilihnya tidak muncul sama sekali.

Tautan yang dibuat server mengikuti domain permintaan, termasuk perjalanan OAuth Google: redirect_uri diturunkan dari domain yang sedang dipakai admin, dan identitas sekolah ikut dalam parameter state, jadi token Google tersimpan di sekolah yang memulai — bukan di sekolah yang kebetulan terpilih di peramban. Setiap domain yang dipakai (portal maupun domain sekolah) harus didaftarkan sebagai Authorized redirect URI di Google Cloud Console dalam bentuk https://<domain>/api/v1/auth/google/callback. Sekolah yang ingin memaksa satu alamat tertentu dapat mengisi google_oauth_redirect_uri di pengaturannya.

Menambah sekolah

Lewat portal dinas, atau dari terminal:

npx tsx scripts/create-sekolah.ts \
  --nama "SD Negeri 1 Contoh" \
  --admin-email admin@sdn1.sch.id \
  --admin-password rahasia123 \
  [--slug sdn-1] [--npsn 20123456] [--domain sdn1.sch.id]

Akun dinas pertama dibuat otomatis saat registry masih kosong. Emailnya dari DINAS_DEFAULT_EMAIL (bawaan dinas@sia.local). Kata sandinya dari DINAS_DEFAULT_PASSWORD, dan bila variabel itu dibiarkan kosong, kata sandi diacak lalu ditampilkan sekali di log saat pertama kali dijalankan, jadi catatlah dari log tersebut. Kata sandi wajib diubah setelah login pertama.

Modul per sekolah

Admin sekolah menyalakan atau mematikan modul di Pengaturan > Modul, misalnya Persuratan, Absensi Siswa, PPDB, atau Aset. Modul yang dimatikan hilang dari menu, route, dan widget dashboard, dan endpoint miliknya menjawab MODUL_NONAKTIF. Data tidak dihapus dan kembali muncul saat modul diaktifkan lagi.

Satu modul, yaitu Alumni, hanya menyembunyikan halamannya. Daftar lulusan memakai endpoint Data Siswa yang sama dengan halaman siswa biasa, jadi tidak ada API yang bisa ditutup tanpa ikut mematikan Data Siswa; lulusan tetap terbaca lewat filter status di sana. Daftar endpoint milik setiap modul ada di src/shared/modules.ts dan dipasang di src/server/app.ts.

Konfigurasi di aplikasi, bukan lewat .env

Konfigurasi seperti S3, Casdoor, Google, perpustakaan, dan e-Presensi tersimpan di Pengaturan masing-masing sekolah. Nama dinas dan alamat portal diatur dari portal dinas. Kunci penanda tangan magic link dan token dokumen dibuat otomatis dan disimpan di registry.

Nilai .env dari instalasi lama dipindahkan sekali saat pertama kali dijalankan, lalu tidak lagi dibaca, dan log boot menyebutkan variabel mana saja yang sudah diabaikan. Yang tetap berasal dari .env hanya MASTER_KEY, SIA_DATA_DIR, setelan proses (PORT, NODE_ENV, TZ), AUTH_MODE bila ingin mengunci metode login untuk seluruh server, dan DINAS_DEFAULT_* untuk akun dinas pertama.

Upgrade dari pemasangan satu sekolah

Tidak ada langkah manual. Saat pertama kali dijalankan, data/sia.db dan data/uploads yang ada didaftarkan sebagai sekolah pertama di tempatnya, nilai .env dipindahkan ke Pengaturan sekolah itu, dan akun dinas bawaan dibuat.

Skrip pemeliharaan

Semua skrip di scripts/ bekerja pada satu sekolah dan memilihnya dengan --sekolah <slug> (atau lewat SIA_SEKOLAH):

npm run audit:arsip -- --sekolah sdn-1
npm run backfill:riwayat -- --sekolah sdn-1 --bulan 2026-07
npx tsx scripts/seed-admin.ts --sekolah sdn-1 admin@sdn1.sch.id rahasia123

Bila hanya ada satu sekolah, opsi itu boleh dikosongkan. Bila ada beberapa dan opsinya tidak diisi, skrip berhenti dan menyebutkan pilihan slug yang ada — tidak pernah menebak sekolah mana yang dimaksud. Skrip baru cukup memakai runScriptForSekolah dari scripts/lib/open-sekolah-db.ts, yang menyediakan sqlite mentah maupun db Drizzle di dalam konteks sekolah terpilih.

Skrip juga menyamakan skema database sekolah saat dibuka, jadi migrasi lama seperti migrate-auth.ts atau add-mapel-columns.ts sekarang hanya melapor "sudah ada"; skema terbaru sudah dipasang otomatis lewat src/server/db/.

Local Development

  1. Salin .env.example ke .env (atau buat file .env baru) dan isi kredensial yang dibutuhkan seperti konfigurasi OAuth, Storage, dan Enkripsi.

  2. Install dependensi untuk proyek:

    npm install
    
  3. (Opsional) Jalankan migrasi database jika Drizzle belum setup:

    npx drizzle-kit push
    
  4. Jalankan server backend (Hono) dan frontend (Vite) secara bersamaan:

    npm run dev:all
    

    Alternatif terpisah:

    • Menjalankan Frontend saja: npm run dev
    • Menjalankan Backend saja: npm run server
  5. Aplikasi frontend dapat diakses di http://localhost:5173 dan backend API di port 3001.

Build Artefak

Untuk mem-build proyek ke production:

npm run build

Perintah ini akan melakukan kompilasi TypeScript (tsc -b) dan mem-build bundle frontend menggunakan Vite (vite build) yang output-nya akan berada di folder dist.

Environment Variables Utama

Beberapa environment variables penting yang perlu Anda atur di .env:

Hanya empat hal yang benar-benar berasal dari .env, karena dibutuhkan sebelum database mana pun dapat dibuka:

  • MASTER_KEY (wajib) — Kunci enkripsi kredensial. Karena melindungi isi database, ia tidak dapat disimpan di dalam database. Buat dengan openssl rand -hex 32; frasa yang lebih pendek dari 16 karakter tetap diterima tetapi diperingatkan saat boot. Kunci dipakai semua sekolah, jadi menggantinya berarti mengenkripsi ulang setiap sekolah
  • SIA_DATA_DIR (opsional, bawaan ./data) — Lokasi registry, database sekolah, unggahan, dan laporan
  • PORT, NODE_ENV, TZ — Setelan proses server. Kunci pengembangan bawaan hanya dipakai bila NODE_ENV=development atau test; di luar itu MASTER_KEY wajib ada, supaya menjalankan server tanpa NODE_ENV tidak diam-diam memakai kunci yang tertulis di kode
  • DINAS_DEFAULT_EMAIL, DINAS_DEFAULT_PASSWORD (opsional) — Hanya dipakai sekali untuk membuat akun dinas pertama

Dua kelompok berikut menyangkut seluruh pemasangan dan karena itu juga berada di .env, bukan di portal:

  • BACKUP_LOCAL_DIR dan/atau BACKUP_S3_BUCKET (+ BACKUP_S3_ENDPOINT, BACKUP_S3_REGION, BACKUP_S3_ACCESS_KEY, BACKUP_S3_SECRET_KEY, BACKUP_S3_PREFIX) — Tujuan backup terjadwal; lihat Backup terjadwal. BACKUP_CRON (bawaan 0 1 * * *) dan BACKUP_KEEP_DAYS (bawaan 30) mengatur jadwal dan masa simpan
  • SIA_TUTUP_MENGANGGUR_MENIT (bawaan 30) — Koneksi database sekolah yang tidak dipakai selama ini ditutup; 0 mempertahankan semua koneksi terbuka
  • SYNC_PRESENSI_PARALEL (bawaan 5) — Berapa sekolah disinkronkan ke e-Presensi sekaligus pada jadwal 07.00, 07.30, dan 15.00. Satu sekolah memakan beberapa detik permintaan HTTP, jadi untuk ratusan sekolah nilai ini yang menentukan apakah sinkronisasi selesai sebelum jadwal berikutnya. Jadwal yang datang saat run sebelumnya belum selesai dilewati, bukan digandakan

Nama dinas dan alamat portal diatur dari portal dinas > Pengaturan. Kunci penanda tangan magic link dan token dokumen dibuat otomatis, disimpan terenkripsi di registry, dan tidak perlu diisi sama sekali.

Cakupan enkripsi

Yang dienkripsi adalah kredensial integrasi (Casdoor, Google, S3, server arsip, perpustakaan, e-Presensi) dan kunci penanda tangan. Data siswa serta berkas unggahan seperti akta dan kartu keluarga tersimpan polos di berkas database dan folder uploads, karena tidak memakai SQLCipher. Lindungi direktori data dan cadangannya dengan izin berkas dan enkripsi pada tingkat penyimpanan.

Variabel di bawah ini hanya berlaku sebagai nilai awal untuk instalasi lama.

  • AUTH_MODE (sso atau local, opsional) — Mengunci metode login; jika tidak diatur, bawaan local dan admin dapat mengubahnya dari halaman Pengaturan
  • CASDOOR_CLIENT_ID, CASDOOR_CLIENT_SECRET, CASDOOR_ENDPOINT, CASDOOR_ORGANIZATION — Konfigurasi SSO Casdoor opsional. Sejak dukungan multi-sekolah, nilai ini hanya diadopsi sekali lalu diatur per sekolah melalui Pengaturan > Keamanan, dan Client Secret disimpan terenkripsi dengan MASTER_KEY
  • GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET — Konfigurasi login Google OAuth
  • GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_PRIVATE_KEY — Kredensial untuk integrasi Google Sheets
  • GOOGLE_DRIVE_FOLDER_ID — Target folder untuk penyimpanan dokumen di GDrive
  • S3_REGION, S3_ENDPOINT, S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY — Konfigurasi penyimpanan cloud S3 / Minio
  • ARSIP_MODE (internal atau eksternal) dan STORAGE_MODE (local atau s3) — Pilihan sumber arsip dan penyimpanan file; bawaan internal dan local, diatur per sekolah melalui Settings
  • DIGITAL_LIBRARY_URL, SIA_INTEGRATION_KEY — Integrasi perpustakaan digital (opsional), diatur per sekolah melalui Pengaturan > Integrasi
  • BACKUP_MAX_UPLOAD_BYTES (opsional) — Batas ukuran berkas backup yang boleh diunggah saat pemulihan; bawaan 2 GiB

Sesudah dipindahkan, ubahlah melalui halaman Pengaturan sekolah.

Instalasi Mandiri (Tanpa Layanan Eksternal)

Konfigurasi bawaan sudah cukup tanpa layanan tambahan: login lokal, arsip internal, dan file disimpan di folder data/. Hanya MASTER_KEY (dan DOC_SECRET di production) yang wajib diisi. SSO, S3, Google, server arsip eksternal, dan perpustakaan digital semuanya opsional dan diaktifkan dari halaman Pengaturan sekolah masing-masing.

Instalasi lama yang sebelumnya memakai server arsip eksternal atau SSO tanpa mengatur arsip_mode/auth_mode secara eksplisit akan otomatis dikunci ke nilai lamanya saat server pertama kali dijalankan setelah pembaruan ini.

Backup & Pemulihan Data

Backup berlaku per sekolah. Setiap sekolah punya database dan folder berkasnya sendiri, jadi satu berkas cadangan berisi tepat satu sekolah dan hanya bisa dipulihkan kembali ke sekolah itu. Fiturnya ada di Pengaturan > Backup Data dan hanya terbuka untuk admin sekolah.

Dinas tidak punya tombol backup. Arsip sekolah berisi data pribadi siswa dan guru, dan wewenang dinas berhenti pada profil sekolah, akun admin, serta rekap angka — jadi mencadangkan sekolah adalah pekerjaan admin sekolah, sementara menyalin seluruh instalasi adalah pekerjaan operator server lewat volume data/ atau backup-cli.

Membuat cadangan

Tombol Buat & unduh backup menghasilkan satu berkas ZIP berisi:

  • sia.db — salinan konsisten database sekolah itu, diambil lewat online backup SQLite (bukan penyalinan berkas biasa), lalu diperiksa dengan integrity_check sebelum dikirim
  • uploads/ dan laporan/ — dokumen yang diunggah dan laporan yang dihasilkan sekolah itu
  • .google-tokens.json — token OAuth Google sekolah itu, bila ada
  • manifest.json dan checksums.txt — keterangan backup, identitas sekolah asal, dan sidik sha256 tiap berkas

registry.db dan folder sekolah lain tidak pernah ikut. Ini penting untuk sekolah pertama, yang direktori datanya sama dengan akar data/: keduanya ada di folder yang sama, dan dikecualikan secara eksplisit baik saat mengarsip maupun saat menukar berkas.

Prosesnya manual: tidak ada penjadwalan, dan tidak ada tumpukan berkas backup yang menumpuk di server. Berkas dibangun di <direktori sekolah>/.backup/tmp/, dialirkan ke peramban, lalu dihapus.

Berkas backup adalah rahasia. Isinya seluruh data pribadi siswa dan guru sekolah itu beserta kredensial integrasi yang tersimpan terenkripsi. Simpan seperti menyimpan kunci server, dan jangan menaruhnya di layanan berbagi berkas yang terbuka.

Dua catatan penting:

  • Kredensial di tabel settings dienkripsi dengan MASTER_KEY. Memulihkan backup di instalasi dengan MASTER_KEY berbeda tetap memulihkan data siswa dan berkas, tetapi kredensial integrasi harus diisi ulang lewat halaman Pengaturan. Manifest menyimpan sidik jari kunci (bukan kuncinya) supaya perbedaan ini terdeteksi sebelum pemulihan dijalankan.
  • Bila STORAGE_MODE=s3, dokumen siswa berada di bucket dan tidak ikut ke dalam arsip. UI memberi peringatan dan manifest menandainya dengan uploadsComplete: false.

Memulihkan

Pemulihan berjalan dua langkah. Berkas yang diunggah pertama-tama diperiksa dan diekstrak ke <direktori sekolah>/.backup/staging/, bukan ke data hidup: struktur ZIP, keamanan nama entri, CRC tiap berkas, sidik sha256 database, integrity_check, keberadaan tabel inti, keberadaan minimal satu akun admin, dan asal sekolah. Arsip milik sekolah lain ditolak di gerbang terakhir itu; arsip dari pemasangan satu sekolah (format lama, tanpa keterangan asal) hanya memberi peringatan yang harus diakui. Kalau ada yang gagal, tidak ada satu pun berkas hidup yang tersentuh.

Bila lolos, setiap peringatan harus diakui satu per satu dan konfirmasinya berupa pengetikan kata PULIHKAN. Setelah itu:

  1. Permintaan untuk sekolah itu ditutup sementara — sekolah lain dan portal dinas tetap melayani — dan tugas terjadwal dijeda.
  2. Koneksi database sekolah itu ditutup, lalu datanya dipindahkan ke <direktori sekolah>/.backup/pre-restore/<waktu>/.
  3. Isi arsip dipindahkan ke tempatnya, dan setiap langkah dicatat di <direktori sekolah>/.backup/restore-state.json.
  4. Database sekolah itu dibuka kembali di proses yang sama. Skema lama dinaikkan saat dibuka, jadi arsip lama pun langsung memakai skema terbaru. Server tidak dimulai ulang.

Karena tabel sessions ikut dipulihkan, Anda akan keluar dari aplikasi dan perlu masuk lagi memakai kata sandi yang berlaku saat backup dibuat. Kalau login memakai SSO sementara kredensial Casdoor hanya tersimpan di database dengan MASTER_KEY berbeda, siapkan satu admin login lokal lebih dulu atau setel AUTH_MODE=local di .env sebelum memulihkan (nilai environment selalu menang atas nilai di database).

Snapshot pra-pemulihan sengaja ditinggal di server sebagai jalan kembali, dan hanya satu generasi yang disimpan per sekolah. Kalau server gagal menyala tiga kali setelah pemulihan, snapshot itu dipakai otomatis untuk mengembalikan keadaan sebelumnya — tanpa pemutus ini, restart: unless-stopped akan berputar selamanya.

Backup terjadwal

Backup lewat halaman Pengaturan bersifat manual dan per sekolah. Untuk banyak sekolah, server dapat mencadangkan semua sekolah aktif beserta registry.db secara terjadwal ke tempat di luar server. Ini pekerjaan operator, jadi tujuannya diatur di .env dan bukan dari portal dinas: arsipnya berisi data pribadi seluruh sekolah, dan wewenang dinas berhenti pada melihat apakah backup terakhir berhasil (portal dinas > Pengaturan > Backup Terjadwal).

BACKUP_CRON=0 1 * * *          # bawaan: 01.00 setiap hari
BACKUP_KEEP_DAYS=30            # salinan lebih tua dari ini dihapus, minimal satu selalu disimpan
BACKUP_LOCAL_DIR=/mnt/backup   # folder di disk lain, dan/atau
BACKUP_S3_BUCKET=sia-backup    # bucket S3 / MinIO / R2
BACKUP_S3_ENDPOINT=https://s3.contoh.id
BACKUP_S3_REGION=auto
BACKUP_S3_ACCESS_KEY=...
BACKUP_S3_SECRET_KEY=...
BACKUP_S3_PREFIX=sia-backup/

Setiap run menghasilkan, per sekolah, ZIP yang sama persis dengan yang diunduh admin dari halaman Pengaturan, di <tujuan>/<slug>/sia-backup-<slug>-<waktu>.zip, sehingga memulihkannya memakai alur pemulihan biasa. Registry disalin apa adanya ke <tujuan>/registry/registry-<waktu>.db. Sekolah yang sedang menjalankan backup atau pemulihan manual dilewati dan tercatat, begitu pula sekolah yang ditolak karena skemanya bermasalah; salinan terakhir yang baik di tujuan tetap ada. Run yang belum selesai saat jadwal berikutnya tiba tidak digandakan.

Yang tidak ikut: MASTER_KEY. Simpan ia bersama rahasia server lain. Tanpa kunci itu, data siswa di arsip tetap bisa dipulihkan tetapi kredensial integrasi yang tersimpan terenkripsi harus diisi ulang. Bucket tujuan harus privat.

Untuk mencoba tujuan baru atau mengambil salinan sebelum perubahan berisiko, jalankan sekali dari terminal dengan variabel yang sama:

npx tsx src/scripts/backup-cli.ts jadwal --data-dir /app/data

Lewat terminal

Semua yang di atas tersedia sebagai perintah, berguna ketika aplikasi tidak bisa dibuka. Setiap perintah bekerja pada satu sekolah, dipilih dengan --sekolah <slug> (boleh dikosongkan bila hanya ada satu sekolah):

npx tsx src/scripts/backup-cli.ts sekolah   --data-dir /app/data
npx tsx src/scripts/backup-cli.ts status    --data-dir /app/data --sekolah sdn-1
npx tsx src/scripts/backup-cli.ts create    --data-dir /app/data --sekolah sdn-1 --out /tmp/sia-backup.zip
npx tsx src/scripts/backup-cli.ts inspect   --data-dir /app/data --sekolah sdn-1 --archive /tmp/sia-backup.zip
npx tsx src/scripts/backup-cli.ts restore   --data-dir /app/data --sekolah sdn-1 --archive /tmp/sia-backup.zip --yes
npx tsx src/scripts/backup-cli.ts rollback  --data-dir /app/data --sekolah sdn-1 --yes
npx tsx src/scripts/backup-cli.ts unlock    --data-dir /app/data --sekolah sdn-1

restore dan rollback menolak berjalan selama proses server masih hidup, supaya tidak berebut dengan antarmuka web.

Struktur Folder

  • src/server/ — Semua kode backend (Hono API, DB config, routing)
    • src/server/tenant/ — Registry sekolah, konteks per permintaan, dan koneksi database tiap sekolah
    • src/server/modules/ — Modul sekolah yang bisa dihidupkan atau dimatikan
    • src/server/db/ — Skema Drizzle ORM, bootstrap, dan migrasi SQLite
    • src/server/routes/ — Endpoint API (auth, siswa, guru, surat, arsip, dll)
    • src/server/utils/ — Utility backend (storage abstraksi, integrasi Google, oauth, parser file)
  • src/pages/ — Halaman/tampilan (Views) aplikasi React frontend
  • src/components/ — Komponen UI reusable (termasuk hasil instalasi shadcn/ui)
  • src/contexts/ — React Context (contoh: AuthContext)
  • src/services/ — Axios / API client logic dari sisi frontend ke backend
  • src/types/ — Definisi TypeScript untuk model data frontend
  • src/scripts/ — Script operasional yang ikut masuk ke image Docker (backfill riwayat, backup-cli)
  • scripts/ — Script utilitas (seperti migrasi data spesifik)
  • drizzle/ — File SQL hasil generate migrasi dari Drizzle ORM
  • data/ — Semua data yang bertahan: sia.db, uploads/, laporan/, dan folder kerja backup .backup/ (spool, staging, snapshot pra-pemulihan)