Deployment
Panduan untuk melakukan deployment Balaka pada VPS berbasis Ubuntu.
Kebutuhan VPS
| Resource | Minimum | Rekomendasi |
|---|---|---|
| OS | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| CPU | 1 vCPU | 2 vCPU |
| RAM | 2 GB | 4 GB |
| Disk | 20 GB SSD | 40 GB SSD |
| Provider | Biznet, DigitalOcean, dll | Biznet VPS |
VPS 2GB cukup untuk single instance. VPS 4GB bisa menjalankan hingga 3 instance (lihat Multi-Instance).
Prasyarat
1. Repository Deployment (Privat)
Konfigurasi deployment (Ansible playbook, inventory, credentials) disimpan di repository terpisah yang bersifat privat. Clone repository tersebut terlebih dahulu:
# Clone deploy repository
git clone <url-repo-deploy> aplikasi-akunting-deploy
cd aplikasi-akunting-deploy
Struktur repository deploy:
aplikasi-akunting-deploy/
├── site.yml # Server setup playbook
├── deploy.yml # Application deployment playbook
├── deploy.sh # Wrapper script (upload JAR)
├── deploy-remote-build.sh # Wrapper script (build di server)
├── inventory.ini
├── clients/
│ ├── artivisi/
│ │ ├── group_vars/all.yml # Credentials & config
│ │ └── themes/artivisi/ # Theme files
│ └── tazkia-stmik/
│ ├── group_vars/all.yml
│ └── themes/tazkia/
└── upgrade-postgresql.yml
2. Konfigurasi Client
Edit clients/<client>/group_vars/all.yml untuk mengisi:
- Database credentials
- Domain name
- Admin user credentials
- Encryption key
- Telegram bot token (opsional)
- Theme settings
Proses Deployment
Langkah 1: Setup Server
Jalankan site.yml untuk menginstall dan mengkonfigurasi:
- Java 25 (Azul Zulu)
- PostgreSQL 18 (dari PGDG repository)
- Nginx dengan SSL (Let's Encrypt)
- Direktori aplikasi
- Systemd service dengan JVM settings
- Script backup dan cron job
ansible-playbook -i inventory.ini site.yml
Langkah 2: Deploy Aplikasi
Ada dua metode deployment:
Metode 1: deploy.sh (Upload JAR)
Build JAR di mesin lokal, upload ke server:
./deploy.sh <client>
Proses yang dilakukan:
- Build JAR lokal (
./mvnw clean package -DskipTests) - Upload JAR ke server via SCP
- Konfigurasi admin user
- Restart service
- Health check
Metode 2: deploy-remote-build.sh (Build di Server)
Build langsung di server (lebih cepat, tanpa upload JAR):
./deploy-remote-build.sh <client>
Proses yang dilakukan:
- SSH ke server
git pullsource code- Build JAR di server
- Restart service
- Health check
Perhatian: Jika git pull gagal karena history divergence (misalnya setelah force push), perbaiki dulu:
ssh <user>@<server> 'cd ~/aplikasi-akunting && git reset --hard origin/main'
Perhatian: Jangan menjalankan deploy.sh ke beberapa client secara bersamaan. Perintah mvn clean yang paralel akan gagal karena konflik pada direktori target/. Deploy secara berurutan.
Langkah 3: Verifikasi
# Cek status service
ssh <user>@<server> 'sudo systemctl status aplikasi-akunting'
# Health check
curl -s http://<server>:10000/actuator/health
# Cek halaman login (via Nginx/SSL)
curl -I https://<domain>/login
# Harus mendapat HTTP 200
Struktur Direktori di Server
/opt/aplikasi-akunting/
├── aplikasi-akunting.jar # Aplikasi
├── application.properties # Konfigurasi
├── documents/ # Upload dokumen
├── backup/ # Backup lokal
├── scripts/
│ ├── backup.sh
│ ├── backup-b2.sh
│ ├── backup-gdrive.sh
│ └── restore.sh
├── backup.conf
├── .backup-key # Encryption key untuk backup
└── .pgpass # PostgreSQL password file
/var/log/aplikasi-akunting/
├── app.log # Application log
├── gc.log # JVM GC log
├── backup.log # Backup log
└── restore.log # Restore log
DNS dan SSL
DNS
Arahkan domain ke IP server:
A balaka.example.com 103.31.204.12
SSL (Let's Encrypt)
SSL dikonfigurasi otomatis oleh Ansible (site.yml). Untuk pengelolaan manual:
# Cek sertifikat
sudo certbot certificates
# Test renewal
sudo certbot renew --dry-run
# Force renewal
sudo certbot renew --force-renewal
# Cek expiry
echo | openssl s_client -servername <domain> \
-connect <domain>:443 2>/dev/null | \
openssl x509 -noout -dates
Certbot auto-renewal sudah dikonfigurasi via systemd timer. Periksa statusnya:
sudo systemctl status certbot.timer
Systemd Service
Aplikasi berjalan sebagai systemd service aplikasi-akunting:
# Start/stop/restart
sudo systemctl start aplikasi-akunting
sudo systemctl stop aplikasi-akunting
sudo systemctl restart aplikasi-akunting
# Status
sudo systemctl status aplikasi-akunting
# Logs (live)
sudo journalctl -u aplikasi-akunting -f
# Logs (100 baris terakhir)
sudo journalctl -u aplikasi-akunting -n 100
JVM Configuration
JVM menggunakan G1GC (default Java 25, optimal untuk heap < 4GB):
-Xms512m -Xmx1024m # Dynamic heap sizing
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=192m
Untuk multi-instance pada VPS 4GB, gunakan heap lebih kecil:
-Xms256m -Xmx384m
GC logging disimpan di /var/log/aplikasi-akunting/gc.log.
Nginx Configuration
| Setting | Value | Keterangan |
|---|---|---|
| worker_processes | auto | Sesuai jumlah CPU core |
| worker_connections | 1024 | Per worker |
| keepalive_timeout | 65s | Connection reuse |
| rate_limit | 10r/s | Per IP, burst 20 |
| gzip | on | Level 5 compression |
Security headers (HSTS, X-Frame-Options, CSP) dikonfigurasi di SSL site template.
# Test konfigurasi
sudo nginx -t
# Reload
sudo systemctl reload nginx
# Access logs
tail -f /var/log/nginx/access.log
First-Run Health Check
Setelah deployment pertama, pastikan:
- Flyway migrations berhasil dijalankan (V001-V004)
- Health endpoint merespons:
curl -s http://localhost:10000/actuator/health # {"status":"UP"} - Login page bisa diakses:
curl -I https://<domain>/login # HTTP/2 200 - Admin user bisa login dengan credentials yang dikonfigurasi di
group_vars/all.yml
Jika aplikasi gagal start, periksa log:
sudo journalctl -u aplikasi-akunting --no-pager | tail -50
Penyebab umum:
- Database connection gagal (cek credentials di
application.properties) - Flyway migration error (lihat Troubleshooting)
- Port 10000 sudah digunakan proses lain
Selanjutnya
Setelah deployment berhasil, lanjutkan ke Konfigurasi untuk mengatur parameter aplikasi.
Konfigurasi
Referensi lengkap konfigurasi aplikasi Balaka. File konfigurasi utama: /opt/aplikasi-akunting/application.properties.
Database
| Property | Environment Variable | Keterangan |
|---|---|---|
spring.datasource.url | DATABASE_URL | JDBC URL PostgreSQL dengan sslmode=require |
spring.datasource.username | DATABASE_USERNAME | Username database |
spring.datasource.password | DATABASE_PASSWORD | Password database |
Contoh:
spring.datasource.url=${DATABASE_URL:jdbc:postgresql://localhost:5432/accountingdb?sslmode=require}
spring.datasource.username=${DATABASE_USERNAME:akunting}
spring.datasource.password=${DATABASE_PASSWORD:}
SSL mode require memastikan koneksi database terenkripsi. Tidak ada fallback ke koneksi plaintext.
Server
| Property | Default | Keterangan |
|---|---|---|
server.port | 10000 | Port HTTP aplikasi |
server.servlet.session.timeout | 15m | Session timeout |
server.servlet.session.cookie.http-only | true | Cookie tidak bisa diakses JavaScript |
server.servlet.session.cookie.same-site | strict | Cookie hanya dikirim untuk same-site request |
Enkripsi
| Property | Environment Variable | Keterangan |
|---|---|---|
app.encryption.key | APP_ENCRYPTION_KEY | Kunci enkripsi AES-256-GCM untuk field PII |
Generate encryption key:
openssl rand -base64 32
Kritis: Simpan key ini di tempat aman. Jika hilang, data PII yang terenkripsi tidak bisa didekripsi.
Telegram Bot
| Property | Environment Variable | Keterangan |
|---|---|---|
telegram.bot.enabled | TELEGRAM_BOT_ENABLED | Aktifkan bot Telegram (true/false) |
telegram.bot.token | TELEGRAM_BOT_TOKEN | Bot token dari @BotFather |
telegram.bot.username | TELEGRAM_BOT_USERNAME | Username bot |
telegram.bot.webhook.url | TELEGRAM_WEBHOOK_URL | URL webhook untuk menerima update |
telegram.bot.webhook.secret-token | TELEGRAM_WEBHOOK_SECRET | Secret token untuk validasi webhook |
Google Cloud Vision (OCR)
| Property | Environment Variable | Keterangan |
|---|---|---|
google.cloud.vision.enabled | GOOGLE_CLOUD_VISION_ENABLED | Aktifkan OCR (true/false) |
google.cloud.vision.credentials-path | GOOGLE_APPLICATION_CREDENTIALS | Path ke service account JSON |
Theme
| Property | Default | Keterangan |
|---|---|---|
app.theme.name | balaka | Nama theme |
app.theme.footer-text | Balaka | Teks footer |
app.theme.dir | data/themes | Direktori theme assets |
Theme assets disajikan dari filesystem eksternal (app.theme.dir). Jika tidak ditemukan di filesystem, akan dicari di classpath. File theme:
<theme-dir>/<theme-name>/
├── logo.svg # Logo untuk background terang
├── logo-dark.svg # Logo untuk background gelap (sidebar)
└── theme.css # Custom CSS
Perhatian: logo-dark.svg harus menggunakan fill warna terang (putih/light). Jangan menggunakan logo biasa di sidebar gelap karena tidak akan terlihat.
Demo Mode
| Property | Environment Variable | Default | Keterangan |
|---|---|---|---|
app.demo-mode | APP_DEMO_MODE | false | Tampilkan banner reset di setiap halaman |
Saat true, setiap halaman menampilkan banner peringatan bahwa ini adalah instance demo yang di-reset setiap malam. Lihat Demo Setup.
Payroll
| Property | Default | Keterangan |
|---|---|---|
app.payroll.template-id | e0000000-...000014 | UUID template jurnal untuk posting payroll |
app.payroll.schedule-cron | 0 30 6 * * * | Jadwal cron pengecekan payroll (06:30 WIB) |
Document Storage
| Property | Default | Keterangan |
|---|---|---|
app.storage.documents.path | data/documents | Direktori penyimpanan dokumen upload |
app.storage.documents.max-file-size | 10485760 | Ukuran maksimal file (10 MB) |
app.storage.documents.allowed-types | image/jpeg,image/png,image/gif,application/pdf | MIME type yang diizinkan |
Remember Me
| Property | Default | Keterangan |
|---|---|---|
app.remember-me.key | dev-remember-me-key | Key untuk hash-based remember-me token |
Perhatian: Ganti default key di production. Gunakan string random yang panjang.
OpenAPI / Swagger
| Property | Default | Keterangan |
|---|---|---|
springdoc.packages-to-scan | com.artivisi.accountingfinance.controller.api | Package yang di-scan |
springdoc.paths-to-match | /api/** | Path pattern untuk API docs |
springdoc.swagger-ui.path | /swagger-ui.html | URL Swagger UI |
Transaction API
| Property | Default | Keterangan |
|---|---|---|
transaction.api.enabled | true | Aktifkan Transaction API |
transaction.api.require-auth | false | Wajibkan autentikasi untuk API |
JVM Heap Settings
Konfigurasi JVM ada di systemd service file. Untuk single instance pada VPS 2GB:
-Xms512m -Xmx1024m
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=192m
Untuk multi-instance pada VPS 4GB (3 instance):
-Xms256m -Xmx384m
-XX:MetaspaceSize=96m
-XX:MaxMetaspaceSize=128m
Budget memori VPS 2GB:
| Komponen | Alokasi | Keterangan |
|---|---|---|
| JVM Heap | 512-1024 MB | Dynamic sizing |
| JVM Metaspace | 128-192 MB | Class metadata |
| PostgreSQL | ~256 MB | shared_buffers + koneksi |
| OS/Buffers | ~512 MB | Page cache, kernel |
Error Handling
| Property | Default | Keterangan |
|---|---|---|
server.error.include-message | never | Sembunyikan pesan error |
server.error.include-stacktrace | never | Sembunyikan stack trace |
server.error.include-exception | false | Sembunyikan nama exception |
server.error.include-binding-errors | never | Sembunyikan validation errors |
Konfigurasi ini memastikan informasi sensitif tidak ditampilkan ke end user.
Contoh application.properties Lengkap (Production)
spring.application.name=accounting-finance
server.port=10000
# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/accountingdb?sslmode=require
spring.datasource.username=akunting
spring.datasource.password=<password>
# JPA
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
# Timezone
spring.jackson.time-zone=Asia/Jakarta
# Thymeleaf
spring.thymeleaf.cache=true
# Encryption
app.encryption.key=<base64-encoded-32-byte-key>
# Remember Me
app.remember-me.key=<random-long-string>
# Session
server.servlet.session.timeout=15m
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=strict
# Theme
app.theme.name=balaka
app.theme.footer-text=Balaka
app.theme.dir=/opt/aplikasi-akunting/themes
# Document storage
app.storage.documents.path=/opt/aplikasi-akunting/documents
# Error handling
server.error.include-message=never
server.error.include-stacktrace=never
server.error.include-exception=false
server.error.include-binding-errors=never
Selanjutnya
Lihat Database untuk konfigurasi PostgreSQL dan prosedur backup.
Database
Panduan konfigurasi PostgreSQL, migrasi Flyway, backup, dan restore.
PostgreSQL Setup
Instalasi
PostgreSQL 18 diinstall dari PGDG repository oleh Ansible (site.yml). Untuk instalasi manual:
# Tambah PGDG repository
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update
# Install PostgreSQL 18
sudo apt install postgresql-18
Buat Database dan User
sudo -u postgres psql
CREATE USER akunting WITH PASSWORD '<password>';
CREATE DATABASE accountingdb OWNER akunting;
GRANT ALL PRIVILEGES ON DATABASE accountingdb TO akunting;
\q
SSL Certificates
Koneksi database menggunakan sslmode=require. Setup SSL untuk PostgreSQL:
# Generate self-signed certificate untuk PostgreSQL
sudo -u postgres openssl req -new -x509 -days 3650 \
-nodes -text \
-out /etc/postgresql/18/main/server.crt \
-keyout /etc/postgresql/18/main/server.key \
-subj "/CN=localhost"
# Set permissions
sudo -u postgres chmod 600 /etc/postgresql/18/main/server.key
Edit postgresql.conf:
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
Restart PostgreSQL:
sudo systemctl restart postgresql
Optimasi PostgreSQL
Konfigurasi yang dioptimasi untuk OLTP workload di VPS kecil:
| Setting | Value | Keterangan |
|---|---|---|
shared_buffers | 128 MB | ~6% RAM untuk shared server |
effective_cache_size | 384 MB | Estimasi OS cache |
work_mem | 4 MB | Memori per-operasi sort |
maintenance_work_mem | 64 MB | VACUUM, CREATE INDEX |
max_connections | 20 | Sesuai HikariCP pool |
random_page_cost | 1.1 | Untuk SSD storage |
Edit /etc/postgresql/18/main/postgresql.conf:
shared_buffers = 128MB
effective_cache_size = 384MB
work_mem = 4MB
maintenance_work_mem = 64MB
max_connections = 20
random_page_cost = 1.1
Autovacuum dikonfigurasi agresif (scale factor 5%) untuk OLTP.
Flyway Migrations
Balaka menggunakan Flyway untuk schema management. Migrasi dijalankan otomatis saat aplikasi start.
Daftar Migrasi
| Versi | File | Isi |
|---|---|---|
| V001 | V001__security.sql | Tabel user, role, session. Seed admin user |
| V002 | V002__core_schema.sql | Schema akuntansi inti (COA, journal, transaksi) |
| V003 | V003__feature_schema.sql | Schema fitur (payroll, asset, inventory, dll) |
| V004 | V004__seed_data.sql | Data awal (template jurnal, komponen gaji, dll) |
Migrasi terletak di src/main/resources/db/migration/.
JPA Validation Mode
spring.jpa.hibernate.ddl-auto=validate
Hibernate hanya memvalidasi bahwa schema cocok dengan entity. Tidak pernah membuat atau mengubah tabel.
Backup
Jadwal Backup
| Tipe | Jadwal | Retensi | Lokasi |
|---|---|---|---|
| Lokal | Harian 02:00 | 7 hari | /opt/aplikasi-akunting/backup/ |
| Backblaze B2 | Harian 03:00 | 4 minggu | Backblaze B2 |
| Google Drive | Harian 04:00 | 12 bulan | Google Drive |
Isi Backup
aplikasi-akunting_20260402_020000.tar.gz
└── aplikasi-akunting_20260402_020000/
├── database.sql # pg_dump output
├── documents.tar.gz # Dokumen upload
└── manifest.json # Metadata backup
Manual Backup
sudo -u akunting /opt/aplikasi-akunting/scripts/backup.sh
Verifikasi backup:
ls -lh /opt/aplikasi-akunting/backup/ | tail -1
Backup dengan pg_dump (Manual)
# Full database dump
sudo -u postgres pg_dump -Fc accountingdb > /tmp/accountingdb.dump
# SQL format (untuk inspeksi)
sudo -u postgres pg_dump accountingdb > /tmp/accountingdb.sql
Cron Job Backup
Cron job dikonfigurasi oleh Ansible. Untuk verifikasi:
sudo crontab -u akunting -l
Contoh output:
0 2 * * * /opt/aplikasi-akunting/scripts/backup.sh >> /var/log/aplikasi-akunting/backup.log 2>&1
0 3 * * * /opt/aplikasi-akunting/scripts/backup-b2.sh >> /var/log/aplikasi-akunting/backup.log 2>&1
0 4 * * * /opt/aplikasi-akunting/scripts/backup-gdrive.sh >> /var/log/aplikasi-akunting/backup.log 2>&1
Restore
Prosedur Restore
# Lihat backup yang tersedia
ls -la /opt/aplikasi-akunting/backup/
# Restore
sudo /opt/aplikasi-akunting/scripts/restore.sh \
/opt/aplikasi-akunting/backup/aplikasi-akunting_20260402_020000.tar.gz
Proses restore:
- Validasi checksum
- Stop aplikasi
- Drop dan recreate database
- Import database dump
- Restore dokumen
- Start aplikasi
Restore dari pg_dump
# Stop aplikasi
sudo systemctl stop aplikasi-akunting
# Drop dan recreate database
sudo -u postgres psql -c "DROP DATABASE IF EXISTS accountingdb;"
sudo -u postgres psql -c "CREATE DATABASE accountingdb OWNER akunting;"
# Restore (custom format)
sudo -u postgres pg_restore -d accountingdb /tmp/accountingdb.dump
# Atau restore (SQL format)
sudo -u postgres psql -d accountingdb < /tmp/accountingdb.sql
# Start aplikasi
sudo systemctl start aplikasi-akunting
Disaster Recovery
- Provision VPS baru
- Jalankan
site.ymluntuk setup server - Copy file backup ke server baru
- Jalankan restore script
| Metrik | Target |
|---|---|
| RTO (Recovery Time Objective) | ~4 jam |
| RPO (Recovery Point Objective) | 24 jam |
Encryption Key Backup
Lokasi encryption key untuk backup: /opt/aplikasi-akunting/.backup-key
Kritis: Simpan key ini di lokasi terpisah. Tanpa key ini, backup terenkripsi tidak bisa dibuka.
Simpan minimal di DUA lokasi:
- Password manager (Bitwarden, 1Password)
- Printed copy di tempat aman
- USB drive terenkripsi
Troubleshooting Migrasi
Flyway Checksum Mismatch
Terjadi saat file migrasi (misal V003) dimodifikasi setelah sudah dijalankan di production. Flyway menyimpan checksum dari migrasi yang sudah dijalankan dan menolak jika berbeda.
Migration checksum mismatch for migration version 003
-> Applied to database : -1535984110
-> Resolved locally : -1414456606
Perbaikan:
- Identifikasi perubahan schema yang ditambahkan ke migrasi:
sudo -u postgres psql -d accountingdb -c "\d <nama_tabel>"
- Terapkan perubahan schema secara manual:
sudo -u postgres psql -d accountingdb \
-c "ALTER TABLE <nama_tabel> ADD COLUMN <nama_kolom> <tipe>;"
- Update checksum di
flyway_schema_history:
sudo -u postgres psql -d accountingdb \
-c "UPDATE flyway_schema_history SET checksum = <checksum_baru> WHERE version = '<versi>';"
Nilai <checksum_baru> adalah yang tertulis di error message sebagai "Resolved locally".
- Restart aplikasi:
sudo systemctl restart aplikasi-akunting
Database Reset (Hapus Semua Data)
Peringatan: Ini menghapus SEMUA data. Hanya gunakan untuk deployment baru.
sudo systemctl stop aplikasi-akunting
sudo -u postgres psql -c "DROP DATABASE IF EXISTS accountingdb;"
sudo -u postgres psql -c "CREATE DATABASE accountingdb OWNER akunting;"
sudo systemctl start aplikasi-akunting
# Flyway akan membuat schema dan seed data dari awal
Upgrade PostgreSQL
Gunakan playbook upgrade-postgresql.yml untuk upgrade major version (misal 17 ke 18):
ansible-playbook -i inventory.ini upgrade-postgresql.yml \
-e "pg_old_version=17 pg_new_version=18"
Playbook akan:
- Stop aplikasi
- Backup database dengan
pg_dump - Install PostgreSQL versi baru dari PGDG
- Konfigurasi cluster baru di port 5432
- Restore database ke cluster baru
- Start aplikasi
- Verifikasi koneksi
Verifikasi setelah upgrade:
sudo -u postgres psql -c "SELECT version();"
sudo -u postgres psql -c "SHOW shared_buffers; SHOW work_mem;"
curl -s http://localhost:10000/actuator/health
Jika perlu rollback, cluster lama masih tersedia di port 5433:
sudo pg_ctlcluster 18 main stop
sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/17/main/postgresql.conf
sudo pg_ctlcluster 17 main start
sudo systemctl restart aplikasi-akunting
Selanjutnya
Lihat Monitoring untuk memantau kesehatan aplikasi.
Monitoring
Panduan pemantauan kesehatan aplikasi, server, dan database Balaka.
Health Check Endpoint
Spring Boot Actuator menyediakan endpoint health check:
curl -s http://localhost:10000/actuator/health
# {"status":"UP"}
Endpoint ini memeriksa:
- Koneksi database
- Disk space
- JVM status
Gunakan endpoint ini untuk monitoring otomatis (UptimeRobot, Uptime Kuma, dll).
Systemd Status
# Status aplikasi
sudo systemctl status aplikasi-akunting
# Status PostgreSQL
sudo systemctl status postgresql
# Status Nginx
sudo systemctl status nginx
# Semua service terkait
sudo systemctl is-active aplikasi-akunting postgresql nginx
Log
Application Log
# Live log via journalctl
sudo journalctl -u aplikasi-akunting -f
# 100 baris terakhir
sudo journalctl -u aplikasi-akunting -n 100
# Log sejak waktu tertentu
sudo journalctl -u aplikasi-akunting --since "2026-04-01 08:00"
# File log
tail -f /var/log/aplikasi-akunting/app.log
Nginx Access/Error Log
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
GC Log
# JVM Garbage Collection log
tail -f /var/log/aplikasi-akunting/gc.log
Backup Log
cat /var/log/aplikasi-akunting/backup.log
Monitoring Eksternal
UptimeRobot / Uptime Kuma
Konfigurasi HTTP monitor:
| Parameter | Value |
|---|---|
| URL | https://<domain>/login |
| Method | GET |
| Expected Status | 200 |
| Interval | 5 menit |
| Alert | Email / Telegram |
Alternatif, monitor health endpoint (perlu expose via Nginx):
| Parameter | Value |
|---|---|
| URL | https://<domain>/actuator/health |
| Keyword | UP |
Perhatian: Actuator endpoint sebaiknya tidak di-expose publik. Gunakan halaman login untuk monitoring eksternal, atau konfigurasi Nginx untuk membatasi akses actuator ke IP tertentu:
location /actuator/health {
proxy_pass http://127.0.0.1:10000;
allow 127.0.0.1;
allow <monitoring-ip>;
deny all;
}
Monitoring Memori
JVM Heap
# Cek penggunaan memori JVM via jcmd
sudo -u akunting jcmd $(pgrep -f aplikasi-akunting.jar) VM.native_memory summary
# Atau via jstat (GC statistics)
sudo -u akunting jstat -gc $(pgrep -f aplikasi-akunting.jar)
Resident Set Size (RSS)
RSS menunjukkan total memori fisik yang digunakan proses JVM:
# RSS dalam KB
ps -o rss= -p $(pgrep -f aplikasi-akunting.jar)
# Dengan format yang lebih readable
ps -o pid,rss,vsz,comm -p $(pgrep -f aplikasi-akunting.jar)
Patokan penggunaan memori normal:
| Metrik | Normal | Perlu investigasi |
|---|---|---|
| RSS | 600-900 MB | > 1200 MB |
| Heap Used | 200-500 MB | > 800 MB (mendekati Xmx) |
Memori Sistem
# Overview memori
free -h
# Detail per proses (top 10)
ps aux --sort=-%mem | head -11
Monitoring Disk
# Disk usage
df -h
# Direktori terbesar
du -sh /opt/aplikasi-akunting/*
du -sh /var/log/aplikasi-akunting/*
du -sh /opt/aplikasi-akunting/backup/*
Alert Disk Space
Buat script monitoring disk:
#!/bin/bash
# /opt/aplikasi-akunting/scripts/check-disk.sh
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "ALERT: Disk usage ${USAGE}% exceeds threshold ${THRESHOLD}%"
# Kirim notifikasi (email, Telegram, dll)
fi
Tambahkan ke cron:
# Cek disk setiap jam
0 * * * * /opt/aplikasi-akunting/scripts/check-disk.sh >> /var/log/aplikasi-akunting/monitoring.log 2>&1
Monitoring Database
Koneksi Aktif
sudo -u postgres psql -d accountingdb \
-c "SELECT count(*) as active_connections FROM pg_stat_activity WHERE datname = 'accountingdb';"
Database Size
sudo -u postgres psql -d accountingdb \
-c "SELECT pg_size_pretty(pg_database_size('accountingdb')) as db_size;"
Slow Queries
sudo -u postgres psql -d accountingdb \
-c "SELECT pid, now() - pg_stat_activity.query_start AS duration, query
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
AND state != 'idle';"
Table Bloat
sudo -u postgres psql -d accountingdb \
-c "SELECT schemaname, relname, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;"
Checklist Monitoring
Harian
- [ ] Backup log menunjukkan sukses
- [ ] Aplikasi berjalan (
systemctl is-active aplikasi-akunting) - [ ] Health check merespons OK
Mingguan
- [ ] Review application log untuk error
- [ ] Cek disk usage (< 85%)
- [ ] SSL certificate > 30 hari valid
- [ ] Cek jumlah koneksi database
Bulanan
- [ ] Test prosedur restore
- [ ] Rotasi backup lama
- [ ] Update system packages (
sudo apt update && sudo apt upgrade) - [ ] Review GC log untuk memory leak
Selanjutnya
Lihat Updates untuk prosedur upgrade aplikasi.
Update & Upgrade
Prosedur untuk upgrade versi aplikasi Balaka.
Versi
Menggunakan calendar versioning (CalVer): YYYY.MM[.PATCH]-RELEASE
Contoh:
2026.03-RELEASE— Release bulanan2026.03.1-RELEASE— Patch release pertama2026.03.2-RELEASE— Patch release kedua
Persiapan Sebelum Update
1. Backup Manual
Selalu lakukan backup sebelum update:
ssh <user>@<server>
sudo -u akunting /opt/aplikasi-akunting/scripts/backup.sh
Verifikasi backup:
ls -lh /opt/aplikasi-akunting/backup/ | tail -1
2. Cek Release Notes
Baca release notes untuk versi yang akan di-deploy:
# Di repository aplikasi
cat docs/releases/<versi>.md
Perhatikan bagian:
- Breaking Changes — perubahan yang memerlukan tindakan manual
- Migration Guide — langkah migrasi yang perlu dilakukan
- Known Issues — masalah yang sudah diketahui
Metode Deployment
Metode 1: deploy-remote-build.sh (Rekomendasi)
Build di server, tanpa upload JAR. Lebih cepat untuk koneksi internet lambat:
cd aplikasi-akunting-deploy
./deploy-remote-build.sh <client>
Proses:
- SSH ke server
git pulluntuk mengambil source code terbaru./mvnw clean package -DskipTestsdi server- Copy JAR ke
/opt/aplikasi-akunting/ - Restart service
- Health check
Perhatian: Jika git pull gagal (misalnya setelah history rewrite):
ssh <user>@<server> 'cd ~/aplikasi-akunting && git reset --hard origin/main'
Metode 2: deploy.sh (Upload JAR)
Build di lokal, upload JAR ke server:
cd aplikasi-akunting-deploy
./deploy.sh <client>
Proses:
- Build JAR lokal (
./mvnw clean package -DskipTests) - Upload JAR via SCP
- Restart service
- Health check
Perhatian: Jangan menjalankan deploy ke beberapa client secara bersamaan. mvn clean paralel akan gagal karena konflik pada direktori target/. Deploy secara berurutan.
Flyway Migration Caveats
Balaka menggunakan konsolidasi migrasi (V001-V004) yang bisa dimodifikasi saat development. Ini memiliki implikasi saat update:
Migrasi Baru (Tidak Bermasalah)
Jika versi baru menambah file migrasi baru (misal V005), Flyway akan menjalankannya otomatis saat aplikasi start. Tidak perlu tindakan manual.
Migrasi yang Dimodifikasi
Jika versi baru memodifikasi migrasi yang sudah dijalankan (misal menambah kolom ke V003), akan terjadi checksum mismatch:
Migration checksum mismatch for migration version 003
-> Applied to database : -1535984110
-> Resolved locally : -1414456606
Perbaikan:
- Identifikasi perubahan schema:
# Diff migrasi antara versi lama dan baru
git diff <old-tag>..<new-tag> -- src/main/resources/db/migration/V003__feature_schema.sql
- Terapkan perubahan manual ke database:
sudo -u postgres psql -d accountingdb \
-c "ALTER TABLE <tabel> ADD COLUMN <kolom> <tipe>;"
- Update checksum:
sudo -u postgres psql -d accountingdb \
-c "UPDATE flyway_schema_history SET checksum = <checksum_baru> WHERE version = '003';"
- Restart aplikasi:
sudo systemctl restart aplikasi-akunting
Verifikasi Setelah Update
# 1. Cek service berjalan
sudo systemctl status aplikasi-akunting
# 2. Health check
curl -s http://localhost:10000/actuator/health
# 3. Cek login page
curl -I https://<domain>/login
# 4. Cek versi (di application log)
sudo journalctl -u aplikasi-akunting | grep "Started" | tail -1
# 5. Login dan verifikasi secara manual
# Buka https://<domain> dan cek fitur-fitur utama
Rollback
Rollback Aplikasi
Deploy Ansible membuat backup JAR sebelumnya:
# Cek backup ada
ls -la /opt/aplikasi-akunting/aplikasi-akunting.jar.backup
# Rollback ke versi sebelumnya
mv /opt/aplikasi-akunting/aplikasi-akunting.jar.backup /opt/aplikasi-akunting/aplikasi-akunting.jar
sudo systemctl restart aplikasi-akunting
# Verifikasi
curl -I http://localhost:10000/login
Rollback Database
Jika update melibatkan perubahan schema yang perlu di-rollback:
sudo /opt/aplikasi-akunting/scripts/restore.sh \
/opt/aplikasi-akunting/backup/<backup-file>.tar.gz
Rollback Strategi
| Skenario | Tindakan |
|---|---|
| Bug di kode, schema tidak berubah | Rollback JAR saja |
| Bug di kode + schema berubah | Restore database + rollback JAR |
| Schema berubah, perlu data baru | Tidak bisa rollback otomatis, perbaiki di versi selanjutnya |
Checklist Update
- [ ] Backup manual di production
- [ ] Baca release notes
- [ ] Deploy ke server
- [ ] Health check OK
- [ ] Login test
- [ ] Verifikasi fitur utama
- [ ] Monitor log 15 menit setelah deploy
Selanjutnya
Lihat Security untuk konfigurasi keamanan.
Keamanan
Konfigurasi keamanan aplikasi Balaka — RBAC, enkripsi, headers, dan audit.
Role-Based Access Control (RBAC)
Balaka menggunakan 6 role dengan permission yang bersifat aditif (user bisa memiliki lebih dari satu role):
Daftar Role
| Role | Display Name | Deskripsi |
|---|---|---|
ADMIN | Administrator | Akses penuh termasuk manajemen user |
OWNER | Pemilik | Semua fitur bisnis, tanpa manajemen user |
ACCOUNTANT | Akuntan | Operasi akuntansi dan laporan |
STAFF | Staf | Operasi harian terbatas (view, buat draft) |
AUDITOR | Auditor | Read-only ke semua laporan |
EMPLOYEE | Karyawan | Akses slip gaji dan profil sendiri saja |
Perbandingan Permission per Role
| Fitur | ADMIN | OWNER | ACCOUNTANT | STAFF | AUDITOR | EMPLOYEE |
|---|---|---|---|---|---|---|
| Dashboard | view | view | view | view | view | - |
| Transaksi | full | full | CRUD + post/void | view + create | view | - |
| Jurnal | full | full | full | view | view | - |
| Laporan | full + export | full + export | full + export | view | view + export | - |
| Laporan Pajak | view + export | view + export | view + export | - | view + export | - |
| COA | CRUD | CRUD | view | view | view | - |
| Template | CRUD | CRUD | CRU | view | view | - |
| Client/Vendor | CRUD | CRUD | CRU | view | view | - |
| Invoice/Bill | full | full | full | view | view | - |
| Payroll | full | full | full (tanpa cancel) | - | view + export | - |
| Asset | full | full | full (tanpa delete) | view | view | - |
| Inventory | full | full | full | view | view | - |
| Bank Recon | full + config | full + config | full (tanpa config) | view | view | - |
| Import Data | yes | yes | - | - | - | - |
| Settings | full | full | view + Telegram | view + Telegram | - | - |
| User Management | full | - | - | - | - | - |
| Audit Log | view | view | - | - | view | - |
| Data Subject Rights | full | - | - | - | - | - |
| Profil Sendiri | full | full | full | full | view | full |
| Slip Gaji Sendiri | view | view | view | view | - | view |
Default Admin User
Migrasi V001 membuat admin default yang di-replace oleh Ansible saat deployment ke production. Credentials dikonfigurasi di clients/<client>/group_vars/all.yml.
Enkripsi Field PII
Data PII (Personally Identifiable Information) dienkripsi di level field menggunakan AES-256-GCM:
app.encryption.key=${APP_ENCRYPTION_KEY:}
Generate key:
openssl rand -base64 32
Field yang dienkripsi termasuk data sensitif karyawan (NIK, NPWP, nomor rekening, dll).
Kritis:
- Simpan encryption key di tempat terpisah dari database
- Jika key hilang, data PII yang terenkripsi tidak bisa dipulihkan
- Jangan pernah mengubah key setelah ada data terenkripsi (data lama tidak akan bisa didekripsi dengan key baru)
Content Security Policy (CSP)
Balaka mengimplementasikan CSP strict dengan dynamic nonce:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-<random>';
style-src 'self' 'nonce-<random>';
img-src 'self' data:;
font-src 'self';
frame-ancestors 'none';
form-action 'self';
Nonce di-generate per request oleh CspNonceFilter dan disematkan di setiap tag <script> dan <style>. Tidak menggunakan unsafe-inline atau unsafe-eval.
Semua komponen Alpine.js menggunakan CSP build — didaftarkan via Alpine.data() di alpine-components.js, tanpa ekspresi inline di template HTML.
Security Headers
| Header | Value | Keterangan |
|---|---|---|
Content-Security-Policy | Dynamic nonce | Mencegah XSS |
X-Frame-Options | DENY | Mencegah clickjacking |
X-Content-Type-Options | nosniff | Mencegah MIME sniffing |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Paksa HTTPS (1 tahun) |
Referrer-Policy | strict-origin-when-cross-origin | Batasi Referer header |
Permissions-Policy | geolocation=(), microphone=(), camera=(), payment=() | Nonaktifkan fitur browser yang tidak diperlukan |
CSRF Protection
CSRF protection aktif untuk semua form-based request. Dikecualikan hanya untuk:
/api/**— menggunakan Bearer token authentication- Telegram webhook — menggunakan secret token
Konfigurasi di SecurityConfig.java:
.csrf(csrf -> csrf
.ignoringRequestMatchers("/*/api/**", "/api/**")
)
Session Management
| Parameter | Value | Keterangan |
|---|---|---|
| Session Timeout | 15 menit | Configurable via server.servlet.session.timeout |
| Cookie HttpOnly | true | Cookie tidak bisa diakses JavaScript |
| Cookie SameSite | strict | Cookie hanya dikirim untuk same-site request |
| Remember Me | 7 hari | Hash-based token |
Password Security
- Hashing: BCrypt (10 rounds)
- Account lockout: Ditangani oleh
AuthenticationEventListener - Password strength: Enforced di UI
API Security
- Bearer token authentication untuk semua
/api/**endpoint - OAuth 2.0 device authorization flow untuk token acquisition
- Token management UI tersedia untuk user
- 401 Unauthorized dikembalikan untuk request API tanpa token (bukan redirect ke login)
SpotBugs / OWASP
Analisis keamanan statis:
# Jalankan SpotBugs
./mvnw spotbugs:check
# Hasil: target/spotbugsXml.xml
Target: 0 issue. Exclusion di spotbugs-exclude.xml harus memiliki justifikasi lengkap.
DAST (Dynamic Application Security Testing)
# Full DAST scan
./mvnw test -Dtest=ZapDastTest
# Quick mode (passive scan saja, ~1 menit)
./mvnw test -Dtest=ZapDastTest -Ddast.quick=true
# Hasil: target/security-reports/zap-*.html
Audit Logging
Aktivitas user dicatat di audit log:
- Login/logout
- Perubahan data
- Akses laporan sensitif
Lihat audit log di menu Pengaturan > Audit Log (memerlukan permission AUDIT_LOG_VIEW).
Data Subject Rights (UU PDP)
Untuk kepatuhan UU Perlindungan Data Pribadi:
| Permission | Fungsi |
|---|---|
DATA_SUBJECT_VIEW | Lihat data pribadi user |
DATA_SUBJECT_EXPORT | Export data pribadi |
DATA_SUBJECT_ANONYMIZE | Anonimisasi data |
Hanya role ADMIN yang memiliki permission ini.
Rekomendasi Hardening Server
- Firewall: Hanya buka port 22 (SSH), 80 (HTTP redirect), 443 (HTTPS)
- SSH: Disable password authentication, gunakan key-based auth
- Fail2ban: Install untuk mencegah brute force SSH
- Automatic updates: Enable unattended-upgrades untuk security patches
- File permissions: Pastikan
application.propertieshanya readable oleh userakunting
# Firewall
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
# SSH key-only auth
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# Fail2ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
# File permissions
sudo chmod 600 /opt/aplikasi-akunting/application.properties
sudo chown akunting:akunting /opt/aplikasi-akunting/application.properties
Selanjutnya
Lihat Multi-Instance untuk menjalankan beberapa instance di satu server.
Multi-Instance
Panduan menjalankan beberapa instance Balaka di satu VPS. Cocok untuk melayani beberapa client pada VPS 4GB.
Arsitektur
Internet
│
▼
Nginx (port 443)
├── client-a.example.com → localhost:10001
├── client-b.example.com → localhost:10002
└── client-c.example.com → localhost:10003
│
PostgreSQL (port 5432)
├── accountingdb_client_a
├── accountingdb_client_b
└── accountingdb_client_c
Kapasitas VPS 4GB
Budget memori untuk 3 instance:
| Komponen | Alokasi | Keterangan |
|---|---|---|
| JVM Instance 1 | 384 MB heap + ~100 MB metaspace | -Xmx384m |
| JVM Instance 2 | 384 MB heap + ~100 MB metaspace | -Xmx384m |
| JVM Instance 3 | 384 MB heap + ~100 MB metaspace | -Xmx384m |
| PostgreSQL | ~256 MB | shared_buffers + koneksi |
| Nginx | ~50 MB | |
| OS/Buffers | ~750 MB | Kernel, page cache |
| Total | ~3.7 GB |
Setup Database
Buat database terpisah untuk setiap instance:
sudo -u postgres psql
CREATE USER client_a WITH PASSWORD '<password_a>';
CREATE DATABASE accountingdb_client_a OWNER client_a;
GRANT ALL PRIVILEGES ON DATABASE accountingdb_client_a TO client_a;
CREATE USER client_b WITH PASSWORD '<password_b>';
CREATE DATABASE accountingdb_client_b OWNER client_b;
GRANT ALL PRIVILEGES ON DATABASE accountingdb_client_b TO client_b;
CREATE USER client_c WITH PASSWORD '<password_c>';
CREATE DATABASE accountingdb_client_c OWNER client_c;
GRANT ALL PRIVILEGES ON DATABASE accountingdb_client_c TO client_c;
\q
Setup Direktori
# Struktur per instance
sudo mkdir -p /opt/aplikasi-akunting-client-a/{documents,backup,scripts,themes}
sudo mkdir -p /opt/aplikasi-akunting-client-b/{documents,backup,scripts,themes}
sudo mkdir -p /opt/aplikasi-akunting-client-c/{documents,backup,scripts,themes}
# Ownership
sudo chown -R akunting:akunting /opt/aplikasi-akunting-client-*
# Log directories
sudo mkdir -p /var/log/aplikasi-akunting-client-{a,b,c}
sudo chown -R akunting:akunting /var/log/aplikasi-akunting-client-*
Konfigurasi per Instance
Setiap instance memiliki application.properties sendiri. Yang berbeda per instance:
# /opt/aplikasi-akunting-client-a/application.properties
server.port=10001
spring.datasource.url=jdbc:postgresql://localhost:5432/accountingdb_client_a?sslmode=require
spring.datasource.username=client_a
spring.datasource.password=<password_a>
app.encryption.key=<key_a>
app.remember-me.key=<remember_me_key_a>
app.theme.name=client-a
app.theme.footer-text=Client A
app.theme.dir=/opt/aplikasi-akunting-client-a/themes
app.storage.documents.path=/opt/aplikasi-akunting-client-a/documents
Perhatikan bahwa server.port harus berbeda untuk setiap instance (10001, 10002, 10003).
Systemd Service per Instance
Buat service file untuk setiap instance:
# /etc/systemd/system/aplikasi-akunting-client-a.service
[Unit]
Description=Balaka Accounting - Client A
After=network.target postgresql.service
[Service]
Type=simple
User=akunting
Group=akunting
WorkingDirectory=/opt/aplikasi-akunting-client-a
ExecStart=/usr/bin/java \
-Xms256m -Xmx384m \
-XX:MetaspaceSize=96m \
-XX:MaxMetaspaceSize=128m \
-Xlog:gc*:file=/var/log/aplikasi-akunting-client-a/gc.log:time,tags:filecount=3,filesize=10m \
-jar /opt/aplikasi-akunting-client-a/aplikasi-akunting.jar \
--spring.config.location=file:/opt/aplikasi-akunting-client-a/application.properties
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=akunting-client-a
[Install]
WantedBy=multi-user.target
Ulangi untuk client-b (port 10002) dan client-c (port 10003). Kemudian:
sudo systemctl daemon-reload
sudo systemctl enable aplikasi-akunting-client-a
sudo systemctl enable aplikasi-akunting-client-b
sudo systemctl enable aplikasi-akunting-client-c
sudo systemctl start aplikasi-akunting-client-a
sudo systemctl start aplikasi-akunting-client-b
sudo systemctl start aplikasi-akunting-client-c
Nginx Vhost per Instance
Buat vhost file terpisah untuk setiap instance:
# /etc/nginx/sites-available/client-a
server {
listen 443 ssl;
server_name client-a.example.com;
ssl_certificate /etc/letsencrypt/live/client-a.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/client-a.example.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
# Rate limiting
limit_req zone=per_ip burst=20 nodelay;
location / {
proxy_pass http://127.0.0.1:10001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name client-a.example.com;
return 301 https://$host$request_uri;
}
Aktifkan vhost:
sudo ln -s /etc/nginx/sites-available/client-a /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Resource Limits (cgroup)
Batasi penggunaan resource per instance menggunakan systemd resource control:
# Tambahkan di bagian [Service] pada systemd unit file
# Batas memori (hard limit)
MemoryMax=600M
MemoryHigh=500M
# Batas CPU (relatif terhadap instance lain)
CPUWeight=100
# Batas proses
TasksMax=100
Setelah mengubah service file:
sudo systemctl daemon-reload
sudo systemctl restart aplikasi-akunting-client-a
Cek penggunaan resource:
# Lihat resource usage per service
systemctl status aplikasi-akunting-client-a
# Baris "Memory:" menunjukkan penggunaan memori
# Atau via cgroup
cat /sys/fs/cgroup/system.slice/aplikasi-akunting-client-a.service/memory.current
Monitoring per Instance
Status Semua Instance
# Cek semua instance sekaligus
for svc in client-a client-b client-c; do
echo "=== $svc ==="
sudo systemctl is-active aplikasi-akunting-$svc
curl -s http://localhost:1000${svc: -1}/actuator/health 2>/dev/null || echo "UNREACHABLE"
done
Log per Instance
# Live log instance tertentu
sudo journalctl -u aplikasi-akunting-client-a -f
# Semua instance
sudo journalctl -u 'aplikasi-akunting-client-*' -f
Memori per Instance
# RSS setiap instance
for svc in client-a client-b client-c; do
PID=$(pgrep -f "aplikasi-akunting-$svc")
RSS=$(ps -o rss= -p $PID 2>/dev/null)
echo "$svc: PID=$PID RSS=${RSS}KB"
done
Backup per Instance
Setiap instance memiliki script backup sendiri yang terhubung ke database yang sesuai. Pastikan backup.conf dan .pgpass di setiap direktori instance sudah dikonfigurasi untuk database yang benar.
Selanjutnya
Lihat Demo Setup untuk menyiapkan instance demo.
Setup Demo
Panduan menyiapkan instance demo Balaka dengan 4 jenis industri, reset otomatis setiap malam.
Arsitektur Demo
Satu VPS 4GB menjalankan 4 instance demo (satu per industri):
| Instance | Port | Domain | Database | Industri |
|---|---|---|---|---|
| demo-it | 10001 | demo-it.example.com | demodb_it | IT Service |
| demo-seller | 10002 | demo-seller.example.com | demodb_seller | Online Seller |
| demo-coffee | 10003 | demo-coffee.example.com | demodb_coffee | Coffee Shop |
| demo-campus | 10004 | demo-campus.example.com | demodb_campus | Campus |
Industry Seed Packs
Setiap industri memiliki paket data awal di industry-seed/<industri>/seed-data/:
industry-seed/
├── it-service/seed-data/
│ ├── 01_company_config.csv
│ ├── 02_chart_of_accounts.csv
│ ├── 03_salary_components.csv
│ ├── 04_journal_templates.csv
│ ├── 05_journal_template_lines.csv
│ ├── ...
│ ├── 18_transactions.csv
│ ├── 20_journal_entries.csv
│ └── MANIFEST.md
├── online-seller/seed-data/
├── coffee-shop/seed-data/
└── campus/seed-data/
Data di-import via DataImportService saat pertama kali setup.
Demo Users
Setiap instance demo memiliki user berikut (di-seed via migrasi atau data import):
| Username | Password | Role | Deskripsi |
|---|---|---|---|
pemilik | pemilik123 | OWNER | Pemilik usaha |
akuntan | akuntan123 | ACCOUNTANT | Akuntan |
pembukuan | pembukuan123 | STAFF | Staf pembukuan |
karyawan | karyawan123 | EMPLOYEE | Karyawan biasa |
auditor | auditor123 | AUDITOR | Auditor read-only |
Catatan: Password demo ini hanya untuk instance demo. Di production, gunakan password yang kuat dan unik.
Demo Mode Banner
Aktifkan demo mode untuk menampilkan banner peringatan:
# application.properties
app.demo-mode=true
Atau via environment variable:
APP_DEMO_MODE=true
Banner ini muncul di setiap halaman, memberitahu pengguna bahwa ini adalah instance demo yang di-reset setiap malam.
Setup Instance Demo
1. Buat Database
sudo -u postgres psql
CREATE USER demo WITH PASSWORD '<password>';
CREATE DATABASE demodb_it OWNER demo;
CREATE DATABASE demodb_seller OWNER demo;
CREATE DATABASE demodb_coffee OWNER demo;
CREATE DATABASE demodb_campus OWNER demo;
GRANT ALL PRIVILEGES ON DATABASE demodb_it TO demo;
GRANT ALL PRIVILEGES ON DATABASE demodb_seller TO demo;
GRANT ALL PRIVILEGES ON DATABASE demodb_coffee TO demo;
GRANT ALL PRIVILEGES ON DATABASE demodb_campus TO demo;
\q
2. Buat Direktori
for industry in it seller coffee campus; do
sudo mkdir -p /opt/demo-$industry/{documents,backup,themes}
sudo chown -R akunting:akunting /opt/demo-$industry
sudo mkdir -p /var/log/demo-$industry
sudo chown -R akunting:akunting /var/log/demo-$industry
done
3. Konfigurasi per Instance
# /opt/demo-it/application.properties
server.port=10001
spring.datasource.url=jdbc:postgresql://localhost:5432/demodb_it?sslmode=require
spring.datasource.username=demo
spring.datasource.password=<password>
app.encryption.key=<key>
app.remember-me.key=<key>
app.demo-mode=true
app.theme.name=balaka
app.theme.footer-text=Balaka Demo - IT Service
app.theme.dir=/opt/demo-it/themes
app.storage.documents.path=/opt/demo-it/documents
4. Systemd Service
Buat service file untuk setiap instance (lihat contoh di Multi-Instance).
5. First Run dan Data Import
Saat pertama dijalankan:
- Flyway akan membuat schema (V001-V004)
- Login sebagai admin
- Buka Pengaturan > Import Data
- Import seed pack sesuai industri
6. Simpan Snapshot Database
Setelah data demo lengkap, buat snapshot untuk nightly reset:
# Dump setiap database demo
for industry in it seller coffee campus; do
sudo -u postgres pg_dump -Fc demodb_$industry > /opt/demo-snapshots/demodb_$industry.dump
done
Nightly Reset
Script Reset
#!/bin/bash
# /opt/demo-snapshots/reset-demo.sh
set -euo pipefail
INDUSTRIES="it seller coffee campus"
SNAPSHOT_DIR="/opt/demo-snapshots"
LOG_FILE="/var/log/demo-reset.log"
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting demo reset" >> "$LOG_FILE"
for industry in $INDUSTRIES; do
DB="demodb_$industry"
SERVICE="demo-$industry"
DUMP="$SNAPSHOT_DIR/$DB.dump"
echo "$(date '+%Y-%m-%d %H:%M:%S') Resetting $DB" >> "$LOG_FILE"
# Stop aplikasi
sudo systemctl stop "$SERVICE"
# Drop dan recreate database
sudo -u postgres psql -c "DROP DATABASE IF EXISTS $DB;"
sudo -u postgres psql -c "CREATE DATABASE $DB OWNER demo;"
# Restore dari snapshot
sudo -u postgres pg_restore -d "$DB" "$DUMP"
# Start aplikasi
sudo systemctl start "$SERVICE"
echo "$(date '+%Y-%m-%d %H:%M:%S') $DB reset complete" >> "$LOG_FILE"
done
echo "$(date '+%Y-%m-%d %H:%M:%S') Demo reset complete" >> "$LOG_FILE"
Set permission:
sudo chmod +x /opt/demo-snapshots/reset-demo.sh
Cron Job
Jadwalkan reset setiap malam pukul 02:00 WIB:
sudo crontab -e
Tambahkan:
0 2 * * * /opt/demo-snapshots/reset-demo.sh >> /var/log/demo-reset.log 2>&1
Verifikasi Reset
# Cek log reset
tail -20 /var/log/demo-reset.log
# Cek semua instance berjalan
for industry in it seller coffee campus; do
echo "=== demo-$industry ==="
sudo systemctl is-active demo-$industry
done
Playwright Demo Data Loaders
Untuk pengembangan dan testing, data demo juga bisa di-load via Playwright test framework menggunakan @TestConfiguration dengan @Profile("functional") dan @PostConstruct:
src/test/java/.../functional/
├── ItServiceTestDataInitializer.java
├── OnlineSellerTestDataInitializer.java
├── CoffeeTestDataInitializer.java
└── CampusTestDataInitializer.java
Initializer ini membaca CSV dari industry-seed/ dan mengimpor data via DataImportService. Berguna untuk:
- Mereproduksi state demo di environment test
- Menjalankan automated test terhadap data demo
Update Snapshot Demo
Jika ada perubahan schema atau data seed:
- Stop semua instance demo
- Reset satu instance dan jalankan migrasi baru
- Import ulang data jika diperlukan
- Buat snapshot baru:
sudo -u postgres pg_dump -Fc demodb_it > /opt/demo-snapshots/demodb_it.dump - Start kembali semua instance
Selanjutnya
Lihat Troubleshooting untuk panduan menyelesaikan masalah umum.
Troubleshooting
Panduan menyelesaikan masalah umum pada deployment Balaka.
Aplikasi Tidak Bisa Start
Diagnosis
# Cek status service
sudo systemctl status aplikasi-akunting
# Cek apakah proses Java berjalan
ps aux | grep java
# Cek apakah port sudah digunakan
sudo netstat -tlnp | grep 10000
# Lihat log terakhir
sudo journalctl -u aplikasi-akunting -n 100 --no-pager
tail -100 /var/log/aplikasi-akunting/app.log
Penyebab Umum
| Gejala | Penyebab | Solusi |
|---|---|---|
Address already in use: 10000 | Port sudah dipakai | Kill proses lama: sudo kill $(sudo lsof -t -i:10000) |
Connection refused: localhost:5432 | PostgreSQL tidak jalan | sudo systemctl start postgresql |
FATAL: password authentication failed | Credentials salah | Cek application.properties |
Migration checksum mismatch | Migrasi dimodifikasi | Lihat bagian Flyway di bawah |
Metaspace OutOfMemoryError | Metaspace penuh | Naikkan -XX:MaxMetaspaceSize |
Flyway Checksum Mismatch
Error:
Migration checksum mismatch for migration version 003
-> Applied to database : -1535984110
-> Resolved locally : -1414456606
Penyebab: File migrasi (misal V003) dimodifikasi setelah sudah dijalankan di database.
Perbaikan:
- Identifikasi perubahan schema (diff antara versi lama dan baru)
- Terapkan perubahan manual ke database:
# Cek schema tabel saat ini
sudo -u postgres psql -d accountingdb -c "\d <nama_tabel>"
# Tambahkan kolom yang belum ada
sudo -u postgres psql -d accountingdb \
-c "ALTER TABLE <nama_tabel> ADD COLUMN <nama_kolom> <tipe>;"
- Update checksum di
flyway_schema_history(gunakan nilai "Resolved locally" dari error message):
sudo -u postgres psql -d accountingdb \
-c "UPDATE flyway_schema_history SET checksum = -1414456606 WHERE version = '003';"
- Restart:
sudo systemctl restart aplikasi-akunting
OOM Kill
Gejala
Aplikasi tiba-tiba mati tanpa log error. Ditemukan di dmesg:
sudo dmesg | grep -i oom
# Out of memory: Killed process <PID> (java) total-vm:...
Penyebab
JVM menggunakan lebih banyak memori daripada yang tersedia di VPS.
Solusi
- Kurangi heap size:
# Untuk VPS 2GB
-Xms256m -Xmx512m
# Untuk multi-instance di VPS 4GB
-Xms256m -Xmx384m
- Cek memory leak:
# RSS saat ini
ps -o rss= -p $(pgrep -f aplikasi-akunting.jar)
# Heap usage
sudo -u akunting jcmd $(pgrep -f aplikasi-akunting.jar) GC.heap_info
- Tambah swap (solusi sementara):
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
- Set cgroup memory limit untuk mencegah OOM kill terhadap proses lain:
# Di systemd service file
MemoryMax=800M
Slow Startup
Gejala
Aplikasi butuh lebih dari 2 menit untuk start.
Diagnosis
# Cek waktu startup di log
sudo journalctl -u aplikasi-akunting | grep "Started" | tail -1
# Cek apakah Flyway migration sedang berjalan
sudo journalctl -u aplikasi-akunting | grep -i "flyway\|migration"
Penyebab dan Solusi
| Penyebab | Solusi |
|---|---|
| Flyway migration pertama kali | Normal, tunggu selesai |
| DNS resolution lambat | Tambah entry di /etc/hosts |
| Disk I/O lambat | Pindah ke SSD |
| GC pause saat startup | Naikkan initial heap (-Xms) |
Port Conflicts
# Cek proses yang menggunakan port 10000
sudo lsof -i :10000
sudo netstat -tlnp | grep 10000
# Kill proses
sudo kill $(sudo lsof -t -i:10000)
# Jika tidak bisa di-kill
sudo kill -9 $(sudo lsof -t -i:10000)
SSL Certificate Renewal
Gejala
Browser menampilkan error sertifikat expired atau Nginx gagal start.
Diagnosis
# Cek status sertifikat
sudo certbot certificates
# Cek expiry
echo | openssl s_client -servername <domain> \
-connect <domain>:443 2>/dev/null | \
openssl x509 -noout -dates
Solusi
# Test renewal
sudo certbot renew --dry-run
# Force renewal
sudo certbot renew --force-renewal
# Restart Nginx
sudo systemctl restart nginx
Jika Auto-Renewal Gagal
# Cek timer certbot
sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot
# Cek log certbot
sudo journalctl -u certbot
# Manual renewal
sudo certbot certonly --nginx -d <domain>
Database Connection Failed
# Cek PostgreSQL berjalan
sudo systemctl status postgresql
# Test koneksi
sudo -u postgres psql -d accountingdb -c "SELECT 1;"
# Cek credentials
grep -i "datasource" /opt/aplikasi-akunting/application.properties
# Cek koneksi aktif (terlalu banyak?)
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'accountingdb';"
# Cek max_connections
sudo -u postgres psql -c "SHOW max_connections;"
Connection Pool Exhausted
Jika semua koneksi terpakai:
# Lihat koneksi aktif
sudo -u postgres psql -c "SELECT pid, state, query_start, query
FROM pg_stat_activity WHERE datname = 'accountingdb';"
# Terminate idle connections
sudo -u postgres psql -c "SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'accountingdb'
AND state = 'idle'
AND query_start < now() - interval '30 minutes';"
CORS Issues
Balaka adalah aplikasi same-origin, CORS dinonaktifkan. Jika muncul CORS error:
- Pastikan akses melalui domain yang sama (bukan mix IP dan domain)
- Pastikan Nginx proxy header dikonfigurasi:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Backup Failed
# Cek log backup
cat /var/log/aplikasi-akunting/backup.log
# Cek disk space
df -h
# Cek pgpass
cat /opt/aplikasi-akunting/.pgpass
# Debug backup script
sudo -u akunting bash -x /opt/aplikasi-akunting/scripts/backup.sh
Testcontainers (Development)
Docker Requirement
Full test suite memerlukan Docker untuk Testcontainers (PostgreSQL, ZAP):
# Cek Docker
docker info
# Jika Docker tidak terinstall
sudo apt install docker.io
sudo usermod -aG docker $USER
Test Gagal karena Testcontainers
# Error: Could not find a valid Docker environment
# Solusi: Pastikan Docker daemon berjalan
sudo systemctl start docker
# Error: Container startup timed out
# Solusi: Pull image terlebih dahulu
docker pull postgres:18
Tips Test
# Full test suite (60-90 menit, SELALU jalankan di background)
nohup ./mvnw test > target/test-output.log 2>&1 &
# JANGAN jalankan beberapa instance test bersamaan
# Test spesifik
./mvnw test -Dtest=NamaTestClass
# Debug dengan browser visible
./mvnw test -Dtest=NamaTestClass -Dplaywright.headless=false -Dplaywright.slowmo=100
Health Check Timeout Saat Deploy
Gejala
Ansible deployment gagal karena health check timeout (30 retry x 5 detik = 2.5 menit).
Diagnosis
# Cek log di server
ssh <user>@<server>
sudo journalctl -u aplikasi-akunting --no-pager | tail -50
Penyebab Umum
| Penyebab | Log Indicator | Solusi |
|---|---|---|
| Flyway error | Migration checksum mismatch | Lihat bagian Flyway di atas |
| Database down | Connection refused: localhost:5432 | Start PostgreSQL |
| Port conflict | Address already in use | Kill proses lama |
| OutOfMemory | java.lang.OutOfMemoryError | Kurangi heap atau upgrade VPS |
Database Reset (Fresh Start)
Peringatan: Menghapus SEMUA data.
sudo systemctl stop aplikasi-akunting
sudo -u postgres psql -c "DROP DATABASE IF EXISTS accountingdb;"
sudo -u postgres psql -c "CREATE DATABASE accountingdb OWNER akunting;"
sudo systemctl start aplikasi-akunting
# Flyway akan membuat schema dan seed data dari awal
Command Reference
Rangkuman command yang sering digunakan:
# Service
sudo systemctl {start|stop|restart|status} aplikasi-akunting
sudo systemctl {start|stop|restart|status} postgresql
sudo systemctl {start|stop|restart|status|reload} nginx
# Logs
sudo journalctl -u aplikasi-akunting -f
tail -f /var/log/aplikasi-akunting/app.log
tail -f /var/log/nginx/access.log
# Database
sudo -u postgres psql -d accountingdb
sudo -u postgres psql -c "SELECT version();"
# Health check
curl -s http://localhost:10000/actuator/health
# SSL
sudo certbot certificates
sudo certbot renew --dry-run
# Backup
sudo -u akunting /opt/aplikasi-akunting/scripts/backup.sh
# Memori
free -h
ps -o rss= -p $(pgrep -f aplikasi-akunting.jar)