432 lines
11 KiB
Markdown
432 lines
11 KiB
Markdown
# Debian 13 App Server Setup
|
|
|
|
Panduan ini untuk menyiapkan server kosong Debian 13 sebagai app server QRIS Soundbox Platform.
|
|
|
|
- App domain: `sms.bizone.id`
|
|
- App user: `qrisapp`
|
|
- App directory: `/opt/qris-soundbox`
|
|
- Env file: `/etc/qris-soundbox/qris-soundbox.env`
|
|
- Local app port: `3000`
|
|
- MQTT broker: `broker.bizone.id`
|
|
|
|
Broker MQTT boleh berada di server lain. Dokumen ini fokus ke server aplikasi.
|
|
|
|
## 1. DNS
|
|
|
|
Pastikan `sms.bizone.id` sudah mengarah ke public IP server app.
|
|
|
|
```bash
|
|
dig +short sms.bizone.id
|
|
curl -4 ifconfig.me
|
|
```
|
|
|
|
## 2. Base Packages
|
|
|
|
Jalankan sebagai root atau user sudo.
|
|
|
|
```bash
|
|
sudo apt update
|
|
sudo apt upgrade -y
|
|
sudo apt install -y \
|
|
ca-certificates \
|
|
curl \
|
|
gnupg \
|
|
git \
|
|
openssl \
|
|
build-essential \
|
|
nginx \
|
|
certbot \
|
|
python3-certbot-nginx \
|
|
postgresql \
|
|
postgresql-contrib \
|
|
ufw
|
|
```
|
|
|
|
## 3. Node.js
|
|
|
|
Gunakan Node.js 22 untuk runtime production.
|
|
|
|
```bash
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
sudo apt install -y nodejs
|
|
node -v
|
|
npm -v
|
|
```
|
|
|
|
## 4. Firewall
|
|
|
|
```bash
|
|
sudo ufw allow OpenSSH
|
|
sudo ufw allow 'Nginx Full'
|
|
sudo ufw enable
|
|
sudo ufw status
|
|
```
|
|
|
|
Jangan buka port `3000` ke internet. Traffic publik masuk lewat Nginx.
|
|
|
|
## 5. App User
|
|
|
|
Buat user khusus untuk menjalankan service. Semua proses aplikasi, install dependency, build, migration, dan systemd runtime dijalankan sebagai `qrisapp`; user `root`/sudo hanya dipakai untuk install paket, membuat direktori, menulis env, Nginx, dan systemd.
|
|
|
|
```bash
|
|
sudo adduser --system --group --home /opt/qris-soundbox qrisapp
|
|
sudo install -d -o qrisapp -g qrisapp -m 750 /opt/qris-soundbox
|
|
sudo install -d -o root -g qrisapp -m 750 /etc/qris-soundbox
|
|
sudo install -d -o qrisapp -g qrisapp -m 750 /var/lib/qris-soundbox/exports
|
|
```
|
|
|
|
Untuk deploy via git/rsync, operator boleh login dengan user sudo biasa, tetapi command yang menyentuh repo/app dijalankan via `sudo -u qrisapp ...`.
|
|
|
|
## 6. PostgreSQL
|
|
|
|
Buat database dan user production.
|
|
|
|
```bash
|
|
DB_PASSWORD="$(openssl rand -hex 32)"
|
|
echo "Simpan DB password ini untuk env: ${DB_PASSWORD}"
|
|
|
|
sudo -u postgres psql -v app_password="$DB_PASSWORD" <<'SQL'
|
|
CREATE USER qris_app WITH PASSWORD :'app_password';
|
|
CREATE DATABASE qris_soundbox_platform OWNER qris_app;
|
|
GRANT ALL PRIVILEGES ON DATABASE qris_soundbox_platform TO qris_app;
|
|
\q
|
|
SQL
|
|
```
|
|
|
|
Kalau user/database sudah pernah dibuat, gunakan reset password:
|
|
|
|
```sql
|
|
ALTER USER qris_app WITH PASSWORD '<password-baru>';
|
|
```
|
|
|
|
## 7. Deploy Code
|
|
|
|
Contoh deploy awal via git:
|
|
|
|
```bash
|
|
sudo -u qrisapp git clone https://git.iptek.co/wirabasalamah/Qris-Soundbox.git /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'cd /opt/qris-soundbox && npm ci'
|
|
sudo -u qrisapp bash -lc 'cd /opt/qris-soundbox && npm run typecheck'
|
|
sudo -u qrisapp bash -lc 'cd /opt/qris-soundbox && npm run build'
|
|
```
|
|
|
|
Jika deploy dari artifact, extract/copy artifact ke `/opt/qris-soundbox`, lalu:
|
|
|
|
```bash
|
|
sudo chown -R qrisapp:qrisapp /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'cd /opt/qris-soundbox && npm ci'
|
|
sudo -u qrisapp bash -lc 'cd /opt/qris-soundbox && npm run build'
|
|
```
|
|
|
|
Catatan: command `npm run typecheck`, `npm run db:migrate`, dan sebagian smoke script membutuhkan dev dependency. Untuk server staging/pilot awal, gunakan `npm ci` penuh agar semua command operasional tersedia. Service production tetap menjalankan hasil build lewat `npm run start:dist`.
|
|
|
|
## 8. Environment
|
|
|
|
Buat env production:
|
|
|
|
```bash
|
|
sudo nano /etc/qris-soundbox/qris-soundbox.env
|
|
```
|
|
|
|
Template awal:
|
|
|
|
```env
|
|
NODE_ENV=production
|
|
PORT=3000
|
|
TRUST_PROXY=true
|
|
JSON_BODY_LIMIT=1mb
|
|
LOG_FORMAT=json
|
|
LOG_LEVEL=info
|
|
|
|
ADMIN_AUTH_ALLOW_LEGACY_TOKEN=false
|
|
ADMIN_DEV_LOGIN_ENABLED=false
|
|
ADMIN_SESSION_SECRET=CHANGE_ME_LONG_RANDOM_ADMIN_SESSION_SECRET
|
|
ADMIN_SESSION_TTL_SECONDS=28800
|
|
|
|
MERCHANT_AUTH_ALLOW_LEGACY_TOKEN=false
|
|
MERCHANT_DEV_LOGIN_ENABLED=false
|
|
MERCHANT_SESSION_SECRET=CHANGE_ME_LONG_RANDOM_MERCHANT_SESSION_SECRET
|
|
MERCHANT_SESSION_TTL_SECONDS=28800
|
|
|
|
DEVICE_AUTH_ALLOW_LEGACY_TOKEN=false
|
|
TRACE_HEADER=x-request-id
|
|
IDEMPOTENCY_TTL_MS=300000
|
|
INTEGRATION_WEBHOOK_SECRET=CHANGE_ME_LONG_RANDOM_WEBHOOK_SECRET
|
|
|
|
MQTT_PUBLISH_MODE=broker
|
|
MQTT_BROKER_URL=mqtts://broker.bizone.id:8883
|
|
MQTT_USERNAME=qris-backend
|
|
MQTT_PASSWORD=CHANGE_ME_MQTT_BACKEND_PASSWORD
|
|
MQTT_CLIENT_ID=qris-platform-backend-prod
|
|
MQTT_CONNECT_TIMEOUT_MS=5000
|
|
MQTT_SUBSCRIBE_ENABLED=true
|
|
MQTT_SUBSCRIBE_TOPICS=devices/+/uplink/#
|
|
MQTT_PUBLISH_FORCE_FAIL_ALL=false
|
|
MQTT_PUBLISH_FORCE_FAIL_DEVICE_IDS=
|
|
MQTT_PUBLISH_DEFAULT_RETRY_INTERVAL_MS=15000
|
|
|
|
QF100_MQTT_BROKER_HOST=broker.bizone.id
|
|
QF100_MQTT_BROKER_PORT=8883
|
|
QF100_MQTT_USERNAME=qris-backend
|
|
QF100_MQTT_PASSWORD=CHANGE_ME_MQTT_BACKEND_PASSWORD
|
|
QF100_MQTT_KEEP_ALIVE_SECONDS=60
|
|
QF100_APP_CONFIG_VERSION=21
|
|
QF100_DYNAMIC_QR_URL=
|
|
QF100_DYNAMIC_QR_GAP_SECONDS=0
|
|
|
|
DYNAMIC_QR_EXPIRY_SCHEDULER_ENABLED=true
|
|
DYNAMIC_QR_EXPIRY_SWEEP_INTERVAL_MS=60000
|
|
DYNAMIC_QR_EXPIRY_SWEEP_LIMIT=100
|
|
|
|
EXPORT_WORKER_ENABLED=true
|
|
EXPORT_WORKER_INTERVAL_MS=2000
|
|
EXPORT_WORKER_BATCH_SIZE=2
|
|
EXPORT_JOB_STALE_RUNNING_MS=900000
|
|
EXPORT_SETTLEMENT_ADJUSTMENT_MAX_ROWS=5000
|
|
EXPORT_STORAGE_DIR=/var/lib/qris-soundbox/exports
|
|
EXPORT_RETENTION_DAYS=30
|
|
|
|
RATE_LIMIT_ENABLED=true
|
|
RATE_LIMIT_LOGIN_WINDOW_MS=60000
|
|
RATE_LIMIT_LOGIN_MAX=20
|
|
RATE_LIMIT_DEVICE_WINDOW_MS=60000
|
|
RATE_LIMIT_DEVICE_MAX=600
|
|
RATE_LIMIT_ADMIN_WRITE_WINDOW_MS=60000
|
|
RATE_LIMIT_ADMIN_WRITE_MAX=300
|
|
|
|
FINANCE_PLATFORM_FEE_BPS=70
|
|
SETTLEMENT_ADJUSTMENT_REQUIRE_APPROVAL=true
|
|
|
|
PGHOST=127.0.0.1
|
|
PGPORT=5432
|
|
PGUSER=qris_app
|
|
PGPASSWORD=CHANGE_ME_STRONG_DB_PASSWORD
|
|
PGDATABASE=qris_soundbox_platform
|
|
DATABASE_URL=postgresql://qris_app:CHANGE_ME_STRONG_DB_PASSWORD@127.0.0.1:5432/qris_soundbox_platform
|
|
```
|
|
|
|
Ganti semua `CHANGE_ME_*` dengan secret production. Untuk generate secret cepat:
|
|
|
|
```bash
|
|
openssl rand -hex 32
|
|
```
|
|
|
|
Lock permission:
|
|
|
|
```bash
|
|
sudo chown root:qrisapp /etc/qris-soundbox/qris-soundbox.env
|
|
sudo chmod 640 /etc/qris-soundbox/qris-soundbox.env
|
|
```
|
|
|
|
## 9. Database Migration
|
|
|
|
```bash
|
|
cd /opt/qris-soundbox
|
|
sudo -u qrisapp env $(sudo cat /etc/qris-soundbox/qris-soundbox.env | xargs) npm run db:migrate
|
|
```
|
|
|
|
Jika env berisi karakter spesial yang membuat `xargs` bermasalah, gunakan systemd service sementara atau jalankan lewat shell yang melakukan `set -a; source ...; set +a`.
|
|
|
|
```bash
|
|
cd /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run db:migrate'
|
|
```
|
|
|
|
## 10. Systemd Service
|
|
|
|
Buat service:
|
|
|
|
```bash
|
|
sudo nano /etc/systemd/system/qris-soundbox.service
|
|
```
|
|
|
|
Isi:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=QRIS Soundbox Platform
|
|
After=network-online.target postgresql.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=qrisapp
|
|
Group=qrisapp
|
|
WorkingDirectory=/opt/qris-soundbox
|
|
EnvironmentFile=/etc/qris-soundbox/qris-soundbox.env
|
|
ExecStart=/usr/bin/npm run start:dist
|
|
Restart=always
|
|
RestartSec=5
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=full
|
|
ReadWritePaths=/opt/qris-soundbox /var/lib/qris-soundbox
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Start service:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable qris-soundbox
|
|
sudo systemctl start qris-soundbox
|
|
sudo systemctl status qris-soundbox --no-pager
|
|
journalctl -u qris-soundbox -f
|
|
```
|
|
|
|
## 11. Nginx
|
|
|
|
Buat config:
|
|
|
|
```bash
|
|
sudo nano /etc/nginx/sites-available/sms.bizone.id
|
|
```
|
|
|
|
Isi:
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name sms.bizone.id;
|
|
|
|
client_max_body_size 1m;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
proxy_http_version 1.1;
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
Enable:
|
|
|
|
```bash
|
|
sudo ln -s /etc/nginx/sites-available/sms.bizone.id /etc/nginx/sites-enabled/sms.bizone.id
|
|
sudo nginx -t
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
## 12. TLS Certificate
|
|
|
|
```bash
|
|
sudo certbot --nginx -d sms.bizone.id
|
|
sudo certbot renew --dry-run
|
|
```
|
|
|
|
## 13. Create Production Users
|
|
|
|
```bash
|
|
cd /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run admin:create-user -- --email <email> --name <name> --role admin --password <strong-password>'
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run merchant:create-user -- --merchant <merchant-id-or-code> --email <email> --name <name> --role owner --password <strong-password>'
|
|
```
|
|
|
|
## 14. Verification
|
|
|
|
```bash
|
|
curl -fsS https://sms.bizone.id/health
|
|
curl -fsS https://sms.bizone.id/health/deep
|
|
curl -I https://sms.bizone.id/
|
|
curl -I https://sms.bizone.id/ui
|
|
curl -I https://sms.bizone.id/ui/soundbox-ops
|
|
```
|
|
|
|
Expected:
|
|
|
|
- `https://sms.bizone.id/` redirects to `/ui/soundbox-ops`.
|
|
- `https://sms.bizone.id/ui` redirects to `/ui/soundbox-ops`.
|
|
- `https://sms.bizone.id/ui/soundbox-ops` returns the Soundbox Ops portal HTML.
|
|
|
|
Test config endpoint shape. Replace `9011001900006` with a serial number already registered and active in Device Registry.
|
|
|
|
```bash
|
|
curl -fsS https://sms.bizone.id/speaker/dev-config \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"dev-model": "QF100",
|
|
"item-number": "00",
|
|
"dev-sn": "9011001900006",
|
|
"hardware-config": "0x0F",
|
|
"fw-version": "1.1.2",
|
|
"fw-build": 13,
|
|
"app-config-version": 21,
|
|
"imei": "12345678123456789",
|
|
"imsi": "310150123456789",
|
|
"iccid": "898600MFSSYYGXXXXXXP"
|
|
}'
|
|
```
|
|
|
|
Expected successful device config response:
|
|
|
|
```json
|
|
{
|
|
"error-code": 0,
|
|
"mqtt": {
|
|
"broker-ip": "broker.bizone.id",
|
|
"broker-port": 8883,
|
|
"client-id": "<device-id>",
|
|
"user-name": "qris-backend",
|
|
"password": "<mqtt-password>",
|
|
"subscribe-topic": "devices/<device-id>/downlink/qf100",
|
|
"publish-topic": "devices/<device-id>/uplink/qf100",
|
|
"keep-alive": 60
|
|
}
|
|
}
|
|
```
|
|
|
|
Run production preflight from server:
|
|
|
|
```bash
|
|
cd /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run deploy:check-env'
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run smoke:mqtt-real'
|
|
```
|
|
|
|
Run full smoke only on staging/controlled environment because it creates and cleans test data:
|
|
|
|
```bash
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run smoke:e2e'
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run ui:qa'
|
|
```
|
|
|
|
## 15. QF100 Notes
|
|
|
|
For QF100 devices, app config server URL should point to:
|
|
|
|
```text
|
|
https://sms.bizone.id/speaker/dev-config
|
|
```
|
|
|
|
The config response will point the device to MQTT broker:
|
|
|
|
```text
|
|
broker.bizone.id:8883
|
|
```
|
|
|
|
If the firmware still has MQTT TLS disabled, either patch firmware TLS or prepare a restricted non-TLS pilot listener. Do not expose unrestricted non-TLS MQTT to the internet.
|
|
|
|
## 16. Routine Ops
|
|
|
|
Useful commands:
|
|
|
|
```bash
|
|
sudo systemctl status qris-soundbox --no-pager
|
|
journalctl -u qris-soundbox -n 200 --no-pager
|
|
sudo nginx -t
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
Backup:
|
|
|
|
```bash
|
|
cd /opt/qris-soundbox
|
|
sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run backup:production -- --out /var/backups/qris --include-mosquitto'
|
|
```
|