# 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 ''; ``` ## 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 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 --name --role admin --password ' sudo -u qrisapp bash -lc 'set -a; source /etc/qris-soundbox/qris-soundbox.env; set +a; npm run merchant:create-user -- --merchant --email --name --role owner --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": "", "user-name": "qris-backend", "password": "", "subscribe-topic": "devices//downlink/qf100", "publish-topic": "devices//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' ```