Dharma ERP — OCI Infrastructure Operations Manual

● Production v1.3 2026-08-01

Live Status

ERP
erp.srigaurangasamaj.com
HTTPS Live
Public Website
srigaurangasamaj.com
HTTPS Live
Auto-Deploy
git push → live
Self-hosted runner
SSL Certificate
all-domains-rsa-2026
Expires 2026-10-30
OCI Load Balancer
dharma-erp-lb
140.245.231.240 · 10 Mbps
Server
ubuntu@10.0.2.89
ap-hyderabad-1 · Private subnet

Quick Reference

TaskHow
Deploy a code changegit push origin main — auto-deploys in ~4 min
Manual deployGitHub → Actions → "Deploy to OCI" → Run workflow → type deploy
SSH to serverCreate Bastion session in OCI Console, then use command in §Bastion
Check containerssudo docker compose ps (on server)
View deploy logsGitHub → repo → Actions tab → latest run
Check runner healthsudo systemctl status actions.runner.* (on server)
Renew SSL certSee Certificate Renewal section — renew before 2026-10-16
Staff Login on websiteTop-right nav (desktop) or hamburger menu (mobile)
🔐

Bastion SSH Access

Bastion sessions expire. Create a fresh session each time — do not reuse old session OCIDs.

Step 1 — Create a Bastion Session

  1. OCI Console → Identity & Security → Bastion → bastion108dharma108erp
  2. Create Session → Session type: Managed SSH
  3. Target host: 10.0.2.89 · OS username: ubuntu
  4. Upload public key: ~/.ssh/dharma-erp-prod.pub
  5. Copy the SSH command shown after the session activates

Step 2 — Connect

Replace <SESSION-OCID> with the OCID from the new session:

ssh -i ~/.ssh/dharma-erp-prod \
  -o ProxyCommand="ssh -i ~/.ssh/dharma-erp-prod -W %h:%p -p 22 \
    <SESSION-OCID>@host.bastion.ap-hyderabad-1.oci.oraclecloud.com" \
  -p 22 ubuntu@10.0.2.89
SSH key: Use ~/.ssh/dharma-erp-prod — not id_ed25519.
🚀

Auto-Deploy via GitHub Actions

Active since 2026-08-01. Every git push to main rebuilds and restarts the ERP and website automatically.

Architecture

git push → GitHub → Actions workflow trigger
                         ↓
               Self-hosted runner (ON the OCI server — /opt/actions-runner)
                         ↓
               git pull → docker compose build → restart → health check

Pipeline

StepCommand
1. Pull latest codegit pull origin main
2. Rebuild imagesdocker compose build fastapi nextjs website
3. Restart containersdocker compose up -d --no-deps fastapi nextjs nginx website
4. Reload nginxdocker compose exec nginx nginx -s reload
5. Health checkcurl -sf http://localhost/health

Runner Maintenance

sudo systemctl status actions.runner.*    # check runner is running
sudo systemctl restart actions.runner.*   # restart if needed
sudo journalctl -u actions.runner.* -f   # view live logs

Re-Installing the Runner (new server)

  1. GitHub → Settings → Actions → Runners → New self-hosted runner (Linux/x64)
  2. Run the config command with the registration token on the server in /opt/actions-runner
  3. sudo ./svc.sh install ubuntu && sudo ./svc.sh start
🔒

ERP HTTPS Setup (Original)

The original ERP cert erp-letsencrypt was stored in OCI Certificate Service and covered only erp.srigaurangasamaj.com. It is no longer active on the listener — superseded by the combined cert below.

🌐

Public Website HTTPS (Multi-Domain)

Live since 2026-08-01. One RSA-2048 cert covers all three domains — no SNI complexity.
OCI LB does not support ECC certs. Always use --keylength 2048 (RSA). ECC causes ssl alert number 40.
OCI LB has two cert types — Certificate Service managed and Load Balancer managed. These cannot be mixed on one listener. Use one combined LB-managed cert for all domains.

Current Certificate

FieldValue
Nameall-domains-rsa-2026
TypeLoad Balancer managed
Domainssrigaurangasamaj.com, www.srigaurangasamaj.com, erp.srigaurangasamaj.com
AlgorithmRSA-2048 · Let's Encrypt · acme.sh DNS-01
Expires2026-10-30 — renew before 2026-10-16

DNS (Hostinger)

NameTypeValue
@A140.245.231.240
wwwA140.245.231.240
erpA140.245.231.240
🔄

Certificate Renewal

📅 Calendar reminder: 2026-10-16 (two weeks before expiry). Late renewal breaks HTTPS on all three domains.
  1. SSH to server via Bastion. Run:
    ~/.acme.sh/acme.sh --issue --dns \
      --yes-I-know-dns-manual-mode-enough-go-ahead-please \
      --server letsencrypt --keylength 2048 \
      -d srigaurangasamaj.com \
      -d www.srigaurangasamaj.com \
      -d erp.srigaurangasamaj.com
  2. Add the three _acme-challenge TXT records to Hostinger DNS (values printed by acme.sh). Wait 5 min.
  3. Run --renew (same flags). Then extract files:
    CERT_DIR=~/.acme.sh/srigaurangasamaj.com_ecc
    cat $CERT_DIR/srigaurangasamaj.com.cer   # leaf cert
    cat $CERT_DIR/fullchain.cer              # chain (intermediates)
    cat $CERT_DIR/srigaurangasamaj.com.key   # RSA private key
  4. OCI Console → LB → Certificates → Add certificate → name all-domains-rsa-YYYYMMDD → type: Load balancer → paste leaf, chain, key.
  5. OCI Console → LB → Listeners → https_443 → Edit → select new cert → Save.
  6. Delete the three _acme-challenge TXT records from Hostinger.
🐳

Docker Compose Stack

Compose file: /opt/dharma-erp/dharma_erp/03_PLATFORM/infrastructure/config/docker-compose.yml

ContainerRoleNotes
dharma-postgresPostgreSQL databaseNever rebuilt by auto-deploy
dharma-fastapiFastAPI backendRebuilt on every deploy
dharma-nextjsERP frontend (Next.js)Rebuilt on every deploy
dharma-websitePublic website (Next.js)Rebuilt on every deploy
dharma-nginxReverse proxyTLS terminates at LB; nginx receives HTTP :80
cd /opt/dharma-erp/dharma_erp/03_PLATFORM/infrastructure/config
sudo docker compose ps                          # check all containers
sudo docker compose logs -f fastapi             # tail backend logs
sudo docker compose exec nginx nginx -s reload  # hot-reload nginx config
🗄

Database Migrations & Seeds

Auto-deploy handles code only — SQL migrations and seeds must be run manually via Bastion.
# Run all migrations:
sudo bash /opt/dharma-erp/dharma_erp/03_PLATFORM/infrastructure/scripts/run-migrations.sh

# Run a specific seed (all seeds are idempotent — safe to re-run):
sudo docker exec -i dharma-postgres psql -U postgres dharma_erp \
  < /opt/dharma-erp/dharma_erp/03_PLATFORM/backend/seeds/013_trial_entity.sql

Validation Checklist

CheckHowExpected
ERP HTTPScurl -I https://erp.srigaurangasamaj.com/health200 OK
ERP HTTP redirectcurl -I http://erp.srigaurangasamaj.com/301 → https://
Public websitecurl -I https://srigaurangasamaj.com/200 OK
www aliascurl -I https://www.srigaurangasamaj.com/200 OK
TLS certBrowser padlock on all 3 domainsValid; expires 2026-10-30
ERP loginhttps://erp.srigaurangasamaj.com/loginadmin / vgsf2026 logs in
Staff Login linkNav top-right on srigaurangasamaj.com"Staff Login" → opens ERP
Containerssudo docker compose psAll 5 Up
Auto-deployPush a commit to mainGitHub Actions green in ~4 min
🔑

Credentials & Reference

🔒 Store secrets in a password manager. Do not commit to the repository.
ResourceValue
ERP domainerp.srigaurangasamaj.com
Public websitesrigaurangasamaj.com · www.srigaurangasamaj.com
LB Public IP140.245.231.240
Server Private IP10.0.2.89
OCI Regionap-hyderabad-1
SSH key~/.ssh/dharma-erp-prod
Bastion namebastion108dharma108erp
Bastion hosthost.bastion.ap-hyderabad-1.oci.oraclecloud.com
SSL cert (OCI)all-domains-rsa-2026 · expires 2026-10-30
DB namedharma_erp
DB userpostgres
GitHub reposrigaurangasamaj108-svg/VGSF-Systems
Runner location/opt/actions-runner/ on OCI server

Common Mistakes

#MistakeSymptomFix
1ECC cert on OCI LBssl alert number 40 / no peer certificateReissue with --keylength 2048
2Mixing cert types on listenerSecond domain has no TLSOne combined LB-managed cert
3Reusing expired Bastion session OCIDSSH connection refusedCreate a new session in OCI Console
4Wrong SSH key (id_ed25519 not dharma-erp-prod)Permission denied (publickey)Use -i ~/.ssh/dharma-erp-prod
5Running seeds via auto-deploySeed runs on every deployAlways run seeds manually via Bastion
6useSearchParams() without Suspense (Next.js)Build exit code 1Wrap component in <Suspense>
🛡

Security Baseline

LayerControl
NetworkOCI LB (public) → private VCN → server (no public IP on instance)
SSH accessOCI Managed Bastion only — no direct inbound SSH
TLSTerminates at OCI LB · RSA-2048 · Let's Encrypt · expires 2026-10-30
HTTP redirectOCI LB rule set http_to_https_redirect (301)
API docs/api/docs returns 403 FORBIDDEN in production
SecretsOCI Vault → loaded by load-secrets.sh at startup
BackupsDaily pg_dump → OCI Object Storage dharma-erp-backups at 02:00 UTC
🔁

Disaster Recovery

Rollback a Bad Deploy

cd /opt/dharma-erp
git log --oneline -10
git checkout <good-commit-hash>
cd dharma_erp/03_PLATFORM/infrastructure/config
sudo docker compose build fastapi nextjs website
sudo docker compose up -d --no-deps fastapi nextjs nginx website

Restore Database from Backup

oci os object list --bucket-name dharma-erp-backups --prefix backups/
oci os object get --bucket-name dharma-erp-backups \
  --name backups/daily/dharma_erp_<DATE>.sql.gz --file /tmp/restore.sql.gz
gunzip /tmp/restore.sql.gz
sudo docker exec -i dharma-postgres psql -U postgres dharma_erp < /tmp/restore.sql
📋

Operational Change Log

DateResourceChange
2026-08-01Website Nav/Footer/MobileAdded Staff Login link → erp.srigaurangasamaj.com
2026-08-01GitHub Actions runnerSelf-hosted runner installed as systemd service; deploy.yml created
2026-08-01OCI LB Listener https_443Updated cert from erp-letsencrypt → all-domains-rsa-2026
2026-08-01OCI LB CertificatesAdded all-domains-rsa-2026 (RSA-2048; 3 domains; expires 2026-10-30)
2026-08-01Hostinger DNSA records @ and www → 140.245.231.240; deleted AAAA and ftp records
2026-07-30OCI LB Rule SetCreated http_to_https_redirect (301)
2026-07-30OCI LB ListenerCreated https_443 port 443 with erp-letsencrypt
2026-07-30OCI Certificate ServiceImported erp-letsencrypt (expires 2026-10-28)
2026-07-30OCI LB dharma-erp-lbCreated Load Balancer; backend 10.0.2.89:80