- TypeScript 93.7%
- HTML 5.1%
- JavaScript 1%
- CSS 0.1%
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> |
||
|---|---|---|
| .agents/rules | ||
| .claude | ||
| .stfolder | ||
| dist | ||
| docs/superpowers | ||
| drizzle | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| components.json | ||
| docker-compose.yml | ||
| DOCKER_DEPLOY.md | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| extract.cjs | ||
| extract.js | ||
| extract_guru.js | ||
| extract_guru.py | ||
| g7kaih-tracker.jsx | ||
| grid.html | ||
| implementation_plan.md | ||
| index.html | ||
| KEPALA MASCOT.png | ||
| Laporan Bulanan November 2025.pdf | ||
| MASCOT.png | ||
| package-lock.json | ||
| package.json | ||
| parsed_guru.txt | ||
| Profil Guru.pdf | ||
| README.md | ||
| refactor.cjs | ||
| replace_url.cjs | ||
| seed_sksm.ts | ||
| task.md | ||
| test-absensi.html | ||
| test-cookie.html | ||
| test-final.html | ||
| test-full-headers.html | ||
| test-rawtoken.html | ||
| test-useragent.html | ||
| test2.html | ||
| test3.html | ||
| test_js.ts | ||
| test_pdf.ts | ||
| test_regex.js | ||
| tsconfig.json | ||
| tsconfig.tsbuildinfo | ||
| Tutorial_Siswa_SITONG.md | ||
| vite.config.ts | ||
| walkthrough.md | ||
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:
- Domain permintaan cocok dengan domain yang didaftarkan untuk sekolah
- Header
X-Sekolah-Id(dipakai internal saat mencetak PDF) - Cookie
sekolah_id, yaitu pilihan pengguna di halaman login - 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
-
Salin
.env.exampleke.env(atau buat file.envbaru) dan isi kredensial yang dibutuhkan seperti konfigurasi OAuth, Storage, dan Enkripsi. -
Install dependensi untuk proyek:
npm install -
(Opsional) Jalankan migrasi database jika Drizzle belum setup:
npx drizzle-kit push -
Jalankan server backend (Hono) dan frontend (Vite) secara bersamaan:
npm run dev:allAlternatif terpisah:
- Menjalankan Frontend saja:
npm run dev - Menjalankan Backend saja:
npm run server
- Menjalankan Frontend saja:
-
Aplikasi frontend dapat diakses di
http://localhost:5173dan backend API di port3001.
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 denganopenssl 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 sekolahSIA_DATA_DIR(opsional, bawaan./data) — Lokasi registry, database sekolah, unggahan, dan laporanPORT,NODE_ENV,TZ— Setelan proses server. Kunci pengembangan bawaan hanya dipakai bilaNODE_ENV=developmentatautest; di luar ituMASTER_KEYwajib ada, supaya menjalankan server tanpaNODE_ENVtidak diam-diam memakai kunci yang tertulis di kodeDINAS_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_DIRdan/atauBACKUP_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(bawaan0 1 * * *) danBACKUP_KEEP_DAYS(bawaan 30) mengatur jadwal dan masa simpanSIA_TUTUP_MENGANGGUR_MENIT(bawaan 30) — Koneksi database sekolah yang tidak dipakai selama ini ditutup;0mempertahankan semua koneksi terbukaSYNC_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(ssoataulocal, opsional) — Mengunci metode login; jika tidak diatur, bawaanlocaldan admin dapat mengubahnya dari halaman PengaturanCASDOOR_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 denganMASTER_KEYGOOGLE_OAUTH_CLIENT_ID,GOOGLE_OAUTH_CLIENT_SECRET— Konfigurasi login Google OAuthGOOGLE_SERVICE_ACCOUNT_EMAIL,GOOGLE_PRIVATE_KEY— Kredensial untuk integrasi Google SheetsGOOGLE_DRIVE_FOLDER_ID— Target folder untuk penyimpanan dokumen di GDriveS3_REGION,S3_ENDPOINT,S3_BUCKET,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY— Konfigurasi penyimpanan cloud S3 / MinioARSIP_MODE(internalataueksternal) danSTORAGE_MODE(localataus3) — Pilihan sumber arsip dan penyimpanan file; bawaaninternaldanlocal, diatur per sekolah melalui SettingsDIGITAL_LIBRARY_URL,SIA_INTEGRATION_KEY— Integrasi perpustakaan digital (opsional), diatur per sekolah melalui Pengaturan > IntegrasiBACKUP_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 denganintegrity_checksebelum dikirimuploads/danlaporan/— dokumen yang diunggah dan laporan yang dihasilkan sekolah itu.google-tokens.json— token OAuth Google sekolah itu, bila adamanifest.jsondanchecksums.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
settingsdienkripsi denganMASTER_KEY. Memulihkan backup di instalasi denganMASTER_KEYberbeda 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 denganuploadsComplete: 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:
- Permintaan untuk sekolah itu ditutup sementara — sekolah lain dan portal dinas tetap melayani — dan tugas terjadwal dijeda.
- Koneksi database sekolah itu ditutup, lalu datanya dipindahkan ke
<direktori sekolah>/.backup/pre-restore/<waktu>/. - Isi arsip dipindahkan ke tempatnya, dan setiap langkah dicatat di
<direktori sekolah>/.backup/restore-state.json. - 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 sekolahsrc/server/modules/— Modul sekolah yang bisa dihidupkan atau dimatikansrc/server/db/— Skema Drizzle ORM, bootstrap, dan migrasi SQLitesrc/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 frontendsrc/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 backendsrc/types/— Definisi TypeScript untuk model data frontendsrc/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 ORMdata/— Semua data yang bertahan:sia.db,uploads/,laporan/, dan folder kerja backup.backup/(spool, staging, snapshot pra-pemulihan)