Balaka Panduan Administrator

Deployment

Panduan untuk melakukan deployment Balaka pada VPS berbasis Ubuntu.

Kebutuhan VPS

ResourceMinimumRekomendasi
OSUbuntu 22.04 LTSUbuntu 24.04 LTS
CPU1 vCPU2 vCPU
RAM2 GB4 GB
Disk20 GB SSD40 GB SSD
ProviderBiznet, DigitalOcean, dllBiznet 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:

  1. Build JAR lokal (./mvnw clean package -DskipTests)
  2. Upload JAR ke server via SCP
  3. Konfigurasi admin user
  4. Restart service
  5. 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:

  1. SSH ke server
  2. git pull source code
  3. Build JAR di server
  4. Restart service
  5. 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

SettingValueKeterangan
worker_processesautoSesuai jumlah CPU core
worker_connections1024Per worker
keepalive_timeout65sConnection reuse
rate_limit10r/sPer IP, burst 20
gziponLevel 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:

  1. Flyway migrations berhasil dijalankan (V001-V004)
  2. Health endpoint merespons:
    curl -s http://localhost:10000/actuator/health
    # {"status":"UP"}
    
  3. Login page bisa diakses:
    curl -I https://<domain>/login
    # HTTP/2 200
    
  4. 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

PropertyEnvironment VariableKeterangan
spring.datasource.urlDATABASE_URLJDBC URL PostgreSQL dengan sslmode=require
spring.datasource.usernameDATABASE_USERNAMEUsername database
spring.datasource.passwordDATABASE_PASSWORDPassword 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

PropertyDefaultKeterangan
server.port10000Port HTTP aplikasi
server.servlet.session.timeout15mSession timeout
server.servlet.session.cookie.http-onlytrueCookie tidak bisa diakses JavaScript
server.servlet.session.cookie.same-sitestrictCookie hanya dikirim untuk same-site request

Enkripsi

PropertyEnvironment VariableKeterangan
app.encryption.keyAPP_ENCRYPTION_KEYKunci 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

PropertyEnvironment VariableKeterangan
telegram.bot.enabledTELEGRAM_BOT_ENABLEDAktifkan bot Telegram (true/false)
telegram.bot.tokenTELEGRAM_BOT_TOKENBot token dari @BotFather
telegram.bot.usernameTELEGRAM_BOT_USERNAMEUsername bot
telegram.bot.webhook.urlTELEGRAM_WEBHOOK_URLURL webhook untuk menerima update
telegram.bot.webhook.secret-tokenTELEGRAM_WEBHOOK_SECRETSecret token untuk validasi webhook

Google Cloud Vision (OCR)

PropertyEnvironment VariableKeterangan
google.cloud.vision.enabledGOOGLE_CLOUD_VISION_ENABLEDAktifkan OCR (true/false)
google.cloud.vision.credentials-pathGOOGLE_APPLICATION_CREDENTIALSPath ke service account JSON

Theme

PropertyDefaultKeterangan
app.theme.namebalakaNama theme
app.theme.footer-textBalakaTeks footer
app.theme.dirdata/themesDirektori 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

PropertyEnvironment VariableDefaultKeterangan
app.demo-modeAPP_DEMO_MODEfalseTampilkan 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

PropertyDefaultKeterangan
app.payroll.template-ide0000000-...000014UUID template jurnal untuk posting payroll
app.payroll.schedule-cron0 30 6 * * *Jadwal cron pengecekan payroll (06:30 WIB)

Document Storage

PropertyDefaultKeterangan
app.storage.documents.pathdata/documentsDirektori penyimpanan dokumen upload
app.storage.documents.max-file-size10485760Ukuran maksimal file (10 MB)
app.storage.documents.allowed-typesimage/jpeg,image/png,image/gif,application/pdfMIME type yang diizinkan

Remember Me

PropertyDefaultKeterangan
app.remember-me.keydev-remember-me-keyKey untuk hash-based remember-me token

Perhatian: Ganti default key di production. Gunakan string random yang panjang.

OpenAPI / Swagger

PropertyDefaultKeterangan
springdoc.packages-to-scancom.artivisi.accountingfinance.controller.apiPackage yang di-scan
springdoc.paths-to-match/api/**Path pattern untuk API docs
springdoc.swagger-ui.path/swagger-ui.htmlURL Swagger UI

Transaction API

PropertyDefaultKeterangan
transaction.api.enabledtrueAktifkan Transaction API
transaction.api.require-authfalseWajibkan 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:

KomponenAlokasiKeterangan
JVM Heap512-1024 MBDynamic sizing
JVM Metaspace128-192 MBClass metadata
PostgreSQL~256 MBshared_buffers + koneksi
OS/Buffers~512 MBPage cache, kernel

Error Handling

PropertyDefaultKeterangan
server.error.include-messageneverSembunyikan pesan error
server.error.include-stacktraceneverSembunyikan stack trace
server.error.include-exceptionfalseSembunyikan nama exception
server.error.include-binding-errorsneverSembunyikan 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:

SettingValueKeterangan
shared_buffers128 MB~6% RAM untuk shared server
effective_cache_size384 MBEstimasi OS cache
work_mem4 MBMemori per-operasi sort
maintenance_work_mem64 MBVACUUM, CREATE INDEX
max_connections20Sesuai HikariCP pool
random_page_cost1.1Untuk 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

VersiFileIsi
V001V001__security.sqlTabel user, role, session. Seed admin user
V002V002__core_schema.sqlSchema akuntansi inti (COA, journal, transaksi)
V003V003__feature_schema.sqlSchema fitur (payroll, asset, inventory, dll)
V004V004__seed_data.sqlData 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

TipeJadwalRetensiLokasi
LokalHarian 02:007 hari/opt/aplikasi-akunting/backup/
Backblaze B2Harian 03:004 mingguBackblaze B2
Google DriveHarian 04:0012 bulanGoogle 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:

  1. Validasi checksum
  2. Stop aplikasi
  3. Drop dan recreate database
  4. Import database dump
  5. Restore dokumen
  6. 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

  1. Provision VPS baru
  2. Jalankan site.yml untuk setup server
  3. Copy file backup ke server baru
  4. Jalankan restore script
MetrikTarget
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:

  1. Identifikasi perubahan schema yang ditambahkan ke migrasi:
sudo -u postgres psql -d accountingdb -c "\d <nama_tabel>"
  1. Terapkan perubahan schema secara manual:
sudo -u postgres psql -d accountingdb \
  -c "ALTER TABLE <nama_tabel> ADD COLUMN <nama_kolom> <tipe>;"
  1. 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".

  1. 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:

  1. Stop aplikasi
  2. Backup database dengan pg_dump
  3. Install PostgreSQL versi baru dari PGDG
  4. Konfigurasi cluster baru di port 5432
  5. Restore database ke cluster baru
  6. Start aplikasi
  7. 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:

ParameterValue
URLhttps://<domain>/login
MethodGET
Expected Status200
Interval5 menit
AlertEmail / Telegram

Alternatif, monitor health endpoint (perlu expose via Nginx):

ParameterValue
URLhttps://<domain>/actuator/health
KeywordUP

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:

MetrikNormalPerlu investigasi
RSS600-900 MB> 1200 MB
Heap Used200-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 bulanan
  • 2026.03.1-RELEASE — Patch release pertama
  • 2026.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:

  1. SSH ke server
  2. git pull untuk mengambil source code terbaru
  3. ./mvnw clean package -DskipTests di server
  4. Copy JAR ke /opt/aplikasi-akunting/
  5. Restart service
  6. 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:

  1. Build JAR lokal (./mvnw clean package -DskipTests)
  2. Upload JAR via SCP
  3. Restart service
  4. 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:

  1. 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
  1. Terapkan perubahan manual ke database:
sudo -u postgres psql -d accountingdb \
  -c "ALTER TABLE <tabel> ADD COLUMN <kolom> <tipe>;"
  1. Update checksum:
sudo -u postgres psql -d accountingdb \
  -c "UPDATE flyway_schema_history SET checksum = <checksum_baru> WHERE version = '003';"
  1. 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

SkenarioTindakan
Bug di kode, schema tidak berubahRollback JAR saja
Bug di kode + schema berubahRestore database + rollback JAR
Schema berubah, perlu data baruTidak 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

RoleDisplay NameDeskripsi
ADMINAdministratorAkses penuh termasuk manajemen user
OWNERPemilikSemua fitur bisnis, tanpa manajemen user
ACCOUNTANTAkuntanOperasi akuntansi dan laporan
STAFFStafOperasi harian terbatas (view, buat draft)
AUDITORAuditorRead-only ke semua laporan
EMPLOYEEKaryawanAkses slip gaji dan profil sendiri saja

Perbandingan Permission per Role

FiturADMINOWNERACCOUNTANTSTAFFAUDITOREMPLOYEE
Dashboardviewviewviewviewview-
TransaksifullfullCRUD + post/voidview + createview-
Jurnalfullfullfullviewview-
Laporanfull + exportfull + exportfull + exportviewview + export-
Laporan Pajakview + exportview + exportview + export-view + export-
COACRUDCRUDviewviewview-
TemplateCRUDCRUDCRUviewview-
Client/VendorCRUDCRUDCRUviewview-
Invoice/Billfullfullfullviewview-
Payrollfullfullfull (tanpa cancel)-view + export-
Assetfullfullfull (tanpa delete)viewview-
Inventoryfullfullfullviewview-
Bank Reconfull + configfull + configfull (tanpa config)viewview-
Import Datayesyes----
Settingsfullfullview + Telegramview + Telegram--
User Managementfull-----
Audit Logviewview--view-
Data Subject Rightsfull-----
Profil Sendirifullfullfullfullviewfull
Slip Gaji Sendiriviewviewviewview-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

HeaderValueKeterangan
Content-Security-PolicyDynamic nonceMencegah XSS
X-Frame-OptionsDENYMencegah clickjacking
X-Content-Type-OptionsnosniffMencegah MIME sniffing
Strict-Transport-Securitymax-age=31536000; includeSubDomainsPaksa HTTPS (1 tahun)
Referrer-Policystrict-origin-when-cross-originBatasi Referer header
Permissions-Policygeolocation=(), 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

ParameterValueKeterangan
Session Timeout15 menitConfigurable via server.servlet.session.timeout
Cookie HttpOnlytrueCookie tidak bisa diakses JavaScript
Cookie SameSitestrictCookie hanya dikirim untuk same-site request
Remember Me7 hariHash-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:

PermissionFungsi
DATA_SUBJECT_VIEWLihat data pribadi user
DATA_SUBJECT_EXPORTExport data pribadi
DATA_SUBJECT_ANONYMIZEAnonimisasi data

Hanya role ADMIN yang memiliki permission ini.

Rekomendasi Hardening Server

  1. Firewall: Hanya buka port 22 (SSH), 80 (HTTP redirect), 443 (HTTPS)
  2. SSH: Disable password authentication, gunakan key-based auth
  3. Fail2ban: Install untuk mencegah brute force SSH
  4. Automatic updates: Enable unattended-upgrades untuk security patches
  5. File permissions: Pastikan application.properties hanya readable oleh user akunting
# 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:

KomponenAlokasiKeterangan
JVM Instance 1384 MB heap + ~100 MB metaspace-Xmx384m
JVM Instance 2384 MB heap + ~100 MB metaspace-Xmx384m
JVM Instance 3384 MB heap + ~100 MB metaspace-Xmx384m
PostgreSQL~256 MBshared_buffers + koneksi
Nginx~50 MB
OS/Buffers~750 MBKernel, 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):

InstancePortDomainDatabaseIndustri
demo-it10001demo-it.example.comdemodb_itIT Service
demo-seller10002demo-seller.example.comdemodb_sellerOnline Seller
demo-coffee10003demo-coffee.example.comdemodb_coffeeCoffee Shop
demo-campus10004demo-campus.example.comdemodb_campusCampus

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):

UsernamePasswordRoleDeskripsi
pemilikpemilik123OWNERPemilik usaha
akuntanakuntan123ACCOUNTANTAkuntan
pembukuanpembukuan123STAFFStaf pembukuan
karyawankaryawan123EMPLOYEEKaryawan biasa
auditorauditor123AUDITORAuditor 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:

  1. Flyway akan membuat schema (V001-V004)
  2. Login sebagai admin
  3. Buka Pengaturan > Import Data
  4. 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:

  1. Stop semua instance demo
  2. Reset satu instance dan jalankan migrasi baru
  3. Import ulang data jika diperlukan
  4. Buat snapshot baru:
    sudo -u postgres pg_dump -Fc demodb_it > /opt/demo-snapshots/demodb_it.dump
    
  5. 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

GejalaPenyebabSolusi
Address already in use: 10000Port sudah dipakaiKill proses lama: sudo kill $(sudo lsof -t -i:10000)
Connection refused: localhost:5432PostgreSQL tidak jalansudo systemctl start postgresql
FATAL: password authentication failedCredentials salahCek application.properties
Migration checksum mismatchMigrasi dimodifikasiLihat bagian Flyway di bawah
Metaspace OutOfMemoryErrorMetaspace penuhNaikkan -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:

  1. Identifikasi perubahan schema (diff antara versi lama dan baru)
  2. 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>;"
  1. 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';"
  1. 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

  1. Kurangi heap size:
# Untuk VPS 2GB
-Xms256m -Xmx512m

# Untuk multi-instance di VPS 4GB
-Xms256m -Xmx384m
  1. 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
  1. 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
  1. 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

PenyebabSolusi
Flyway migration pertama kaliNormal, tunggu selesai
DNS resolution lambatTambah entry di /etc/hosts
Disk I/O lambatPindah ke SSD
GC pause saat startupNaikkan 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:

  1. Pastikan akses melalui domain yang sama (bukan mix IP dan domain)
  2. 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

PenyebabLog IndicatorSolusi
Flyway errorMigration checksum mismatchLihat bagian Flyway di atas
Database downConnection refused: localhost:5432Start PostgreSQL
Port conflictAddress already in useKill proses lama
OutOfMemoryjava.lang.OutOfMemoryErrorKurangi 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)