Lewati ke konten utama

Poslite Service โ€” VM Deploy Runbook

Status: ๐ŸŸข DEPLOYED
Owner: Backend Lead
Port: 8096
DB: db_kesles_merchant_poslite (10 tabel: 6 catalog + 4 sales)

Dokumen terkait:

  • services/poslite_service/README.md
  • deploy/systemd/poslite-service.service
  • services/whatsapp/deploy-runbook.md (notification/whatsapp/deploy-runbook) โ€” pola sister service yang ditiru
  • services/email/deploy-runbook.md (notification/email/deploy-runbook) โ€” pola sister service

1. VM topologyโ€‹

ItemValue
Hostptikn3-vm.cluster-vps.dalang.io
User / Groupenalfarid / enalfarid
Folder root/home/enalfarid/kesles_merchant/merchant_poslite/
Binaryposlite-service (Linux ELF amd64 stripped)
Env file.env.production (chmod 644, gitignored)
Systemd unit/etc/systemd/system/poslite-service.service
Listen port127.0.0.1:8096 (loopback, diakses core_api sebagai reverse proxy)
Logssudo journalctl -u poslite-service [-f] [--since="1 hour ago"]

Pattern folder + systemd unit identik dengan merchant_whatsapp/ + merchant_email/ supaya operator tidak belajar layout baru per service.


2. Initial deploy (first-time setup)โ€‹

2.1 Build binary di laptopโ€‹

cd ~/Macbook\ pro/development/kesles_merchant/services/poslite_service

# Cross-compile Linux amd64 stripped
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build \
-trimpath \
-ldflags="-s -w" \
-o dist/poslite-service \
./cmd/server

# Verifikasi
ls -lh dist/poslite-service
file dist/poslite-service # โ†’ ELF 64-bit LSB executable, x86-64, statically linked

2.2 Setup database (WAJIB โ€” DB baru belum ada di VM)โ€‹

Database db_kesles_merchant_poslite harus dibuat terlebih dahulu.

Buat database (jalankan sebagai superuser):

CREATE DATABASE db_kesles_merchant_poslite
OWNER kesles
ENCODING 'UTF8';

Apply init scripts:

DSN="postgres://kesles:<PASSWORD>@10.8.0.1:5432/db_kesles_merchant_poslite?sslmode=disable"

psql "$DSN" -f merchant_database/db_kesles_merchant_poslite/init/001_create_extensions.sql
psql "$DSN" -f merchant_database/db_kesles_merchant_poslite/init/002_create_schemas.sql

Apply migrations (urutan wajib):

psql "$DSN" -f merchant_database/db_kesles_merchant_poslite/migrations/v1/001_schema_migrations_tracker.sql
psql "$DSN" -f merchant_database/db_kesles_merchant_poslite/migrations/v1/002_catalog_tables.sql
psql "$DSN" -f merchant_database/db_kesles_merchant_poslite/migrations/v1/003_sales_tables.sql

Verifikasi:

psql "$DSN" -c "SELECT schema_name FROM information_schema.schemata WHERE schema_name IN ('catalog','sales');"
# Expected: 2 rows โ€” catalog, sales

psql "$DSN" -c "SELECT tablename FROM pg_tables WHERE schemaname = 'catalog';"
# Expected: outlets, product_categories, products, product_variants, product_stock, stock_movements

2.3 Create folder + env di VMโ€‹

ssh enalfarid@ptikn3-vm.cluster-vps.dalang.io
mkdir -p ~/kesles_merchant/merchant_poslite/
cd ~/kesles_merchant/merchant_poslite/

Upload services/poslite_service/.env.production ke folder ini, lalu:

chmod 644 .env.production

Env wajib di production (kalau POSTGRES_DSN kosong/salah, service panic saat ping DB di startup dan systemd masuk restart loop):

EnvNilai
APP_ENV=productionstatic
APP_PORT=8096static
POSTGRES_DSNpostgres://kesles:...@10.8.0.1:5432/db_kesles_merchant_poslite?sslmode=disable
JWT_SECRETsama dengan auth_service + core_api
JWT_ISSUER=kesles-merchant-authstatic
INTERNAL_API_KEYharus identik dengan POSLITE_SERVICE_INTERNAL_KEY di core_api
CORE_API_URL=http://127.0.0.1:8080static
INTERNAL_CORE_API_KEYsama dengan INTERNAL_API_KEY di core_api
INVENTORY_SERVICE_URL=http://127.0.0.1:8084static
INVENTORY_SERVICE_KEYsama dengan INVENTORY_SERVICE_API_KEY di core_api

2.4 Upload binaryโ€‹

Upload services/poslite_service/dist/poslite-service ke ~/kesles_merchant/merchant_poslite/, lalu:

chmod 755 ~/kesles_merchant/merchant_poslite/poslite-service

2.5 Install systemd unitโ€‹

Buat /etc/systemd/system/poslite-service.service:

[Unit]
Description=Kesles Poslite Service (katalog produk + POS merchant)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=enalfarid
Group=enalfarid
WorkingDirectory=/home/enalfarid/kesles_merchant/merchant_poslite
EnvironmentFile=/home/enalfarid/kesles_merchant/merchant_poslite/.env.production
ExecStart=/home/enalfarid/kesles_merchant/merchant_poslite/poslite-service
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=poslite-service

# Hardening (selaras firebase + whatsapp + email)
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths=/home/enalfarid/kesles_merchant/merchant_poslite

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable poslite-service
sudo systemctl start poslite-service
sudo systemctl status poslite-service # Expected: active (running)

2.6 Deploy core_api (aktifkan route /merchant/poslite/)โ€‹

Binary core_api perlu di-update karena route /merchant/poslite/ baru ditambahkan. Binary sudah di-build di merchant_core_api/dist/core-api.

Tambah 2 baris ke /home/enalfarid/kesles_merchant/merchant_core_api/.env.production:

POSLITE_SERVICE_BASE_URL=http://127.0.0.1:8096
POSLITE_SERVICE_INTERNAL_KEY=5998e19e06b76ed318b59c95b1fbfefa763e82a79eb26ceb72c18388895d119c

Upload binary core_api + restart:

sudo systemctl restart core-api

2.7 Smoke test post-deployโ€‹

# 1. Liveness
curl -s http://127.0.0.1:8096/health
# Expected: {"service":"poslite-service","status":"ok"}

# 2. Readiness (DB ping)
curl -s http://127.0.0.1:8096/ready
# Expected: {"status":"ready"}

# 3. Auth wall โ€” tanpa key โ†’ 401
curl -s http://127.0.0.1:8096/poslite/catalog/categories \
-H "X-Merchant-ID: test"
# Expected: {"error":"unauthorized"} atau 401

# 4. Test via core_api proxy (butuh JWT dari app)
curl -s https://api-merchant.kesles.com/merchant/poslite/catalog/categories \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "X-Merchant-ID: <MERCHANT_UUID>"
# Expected: {"categories":[]}

3. Re-deploy (update binary)โ€‹

# 1. Build di laptop
cd ~/Macbook\ pro/development/kesles_merchant/services/poslite_service
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o dist/poslite-service ./cmd/server

# 2. Upload binary via FTP ke merchant_poslite/

# 3. Restart di VM
sudo systemctl restart poslite-service

# 4. Verify (jangan skip โ€” kalau startup gagal ping DB, systemd restart loop)
sudo systemctl status poslite-service && curl -s http://127.0.0.1:8096/ready

4. Env rotationโ€‹

4.1 Rotate INTERNAL_API_KEY (paired dengan core_api)โ€‹

Service ini + caller core_api harus rotate sekaligus supaya tidak ada window auth mismatch.

# 1. Generate new key
NEW_KEY=$(openssl rand -hex 32)

# 2. Di VM poslite: update .env.production
# โ†’ INTERNAL_API_KEY=<NEW_KEY>

# 3. Di VM core_api: update .env.production
# โ†’ POSLITE_SERVICE_INTERNAL_KEY=<NEW_KEY>

# 4. Restart kedua service hampir bersamaan
sudo systemctl restart poslite-service && sudo systemctl restart core-api

5. Monitoringโ€‹

# Real-time log
sudo journalctl -u poslite-service -f

# Error 24 jam terakhir
sudo journalctl -u poslite-service --since "24 hours ago" | grep '"level":"ERROR"'

# Cek port listening
sudo ss -tlnp | grep :8096
EventThresholdAction
/ready 503 > 1 minDB unreachableCek POSTGRES_DSN + DB host network
poslite_proxy_upstream_failed di core_api logService downCek status poslite-service
bind: address already in use saat startPort 8096 dipakai proses lainsudo ss -lntp | grep 8096

6. Troubleshootingโ€‹

Service refuse startโ€‹

sudo systemctl status poslite-service
sudo journalctl -u poslite-service --since "5 minutes ago"
LogPenyebabFix
poslite-service: failed to open DBPOSTGRES_DSN kosong / format salahedit .env.production, restart
poslite-service: failed to ping DBPOSTGRES_DSN salah host / DB downcek DB topology
bind: address already in useport 8096 dipakai proses lainsudo ss -lntp | grep 8096, kill atau ganti port

core_api return 404 untuk /merchant/poslite/*โ€‹

Binary core_api lama belum punya route. Upload binary baru + restart core_api.

core_api return 502 bad_gatewayโ€‹

poslite-service tidak running. Cek sudo systemctl status poslite-service.

core_api return 503 service_unavailableโ€‹

POSLITE_SERVICE_BASE_URL belum di-set di core_api .env.production. Tambah + restart core_api.


7. Rollbackโ€‹

# Stop service
sudo systemctl stop poslite-service

# Rollback binary ke commit sebelumnya (di laptop)
cd ~/Macbook\ pro/development/kesles_merchant/services/poslite_service
git log --oneline -5
git checkout <commit-hash> -- .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o dist/poslite-service ./cmd/server
# Upload + restart
git checkout main -- .

Kalau poslite-service down, core_api route /merchant/poslite/* return 502 bad_gateway โ€” tidak mempengaruhi fitur lain (keypad, transfer, profil, dsb).


8. Disaster recoveryโ€‹

Service stateless โ€” semua state di Postgres. Rebuild scenario:

  1. Re-provision VM (atau VM baru)
  2. Setup user enalfarid + folder ~/kesles_merchant/merchant_poslite/
  3. Upload binary + .env.production
  4. Install systemd unit dari ยง2.5
  5. systemctl enable && start
  6. Update core_api POSLITE_SERVICE_BASE_URL kalau VM pindah host

RTO target: <30 menit (kalau credential + binary tersedia). RPO: 0 (Postgres replicated).


9. Dokumen referensiโ€‹

  • services/whatsapp/deploy-runbook.md (notification/whatsapp/deploy-runbook) โ€” pola referensi
  • services/email/deploy-runbook.md (notification/email/deploy-runbook) โ€” pola referensi
  • deploy/systemd/poslite-service.service โ€” systemd template
  • merchant_database/db_kesles_merchant_poslite/ โ€” migrasi DB