From 1274f2b82299dc22e7da09dd74be781eb5f5ae47 Mon Sep 17 00:00:00 2001 From: Wira Irawan Date: Sun, 17 May 2026 05:52:09 +0700 Subject: [PATCH] Harden login and refresh production deploy guide --- deploy/nginx/abelbirdnest.id.http.conf | 26 ++ deploy/systemd/abelbirdnest-web.service | 4 +- docs/deploy-production.md | 378 ++++++++++-------- src/components/layout/app-shell.tsx | 3 + src/features/auth/components/login-client.tsx | 41 +- 5 files changed, 255 insertions(+), 197 deletions(-) create mode 100644 deploy/nginx/abelbirdnest.id.http.conf diff --git a/deploy/nginx/abelbirdnest.id.http.conf b/deploy/nginx/abelbirdnest.id.http.conf new file mode 100644 index 0000000..7a387f5 --- /dev/null +++ b/deploy/nginx/abelbirdnest.id.http.conf @@ -0,0 +1,26 @@ +server { + listen 80; + listen [::]:80; + server_name abelbirdnest.id www.abelbirdnest.id; + + client_max_body_size 20m; + + location / { + proxy_pass http://127.0.0.1:3007; + 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 http; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 120s; + } + + location /api/v1/health { + proxy_pass http://127.0.0.1:3007; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto http; + } +} diff --git a/deploy/systemd/abelbirdnest-web.service b/deploy/systemd/abelbirdnest-web.service index 2be85a6..d055c0b 100644 --- a/deploy/systemd/abelbirdnest-web.service +++ b/deploy/systemd/abelbirdnest-web.service @@ -4,10 +4,10 @@ After=network.target postgresql.service [Service] Type=simple -WorkingDirectory=/var/www/abelbirdnest-web +WorkingDirectory=/var/www/abelbirdnest-web/AbelBirdNest-Stock Environment=NODE_ENV=production Environment=PORT=3007 -EnvironmentFile=/var/www/abelbirdnest-web/.env.production +EnvironmentFile=/var/www/abelbirdnest-web/AbelBirdNest-Stock/.env.production ExecStart=/usr/bin/npm run start Restart=always RestartSec=5 diff --git a/docs/deploy-production.md b/docs/deploy-production.md index 1d9b86e..58d134f 100644 --- a/docs/deploy-production.md +++ b/docs/deploy-production.md @@ -1,6 +1,6 @@ # Deploy Production -Dokumen ini menyiapkan deploy production untuk: +Panduan ini untuk deploy production dengan asumsi: - domain `abelbirdnest.id` - reverse proxy `nginx` @@ -8,26 +8,26 @@ Dokumen ini menyiapkan deploy production untuk: - database `PostgreSQL` - source code dari git `https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git` - user service khusus `abelbirdnest` +- repo berada di: + `/var/www/abelbirdnest-web/AbelBirdNest-Stock` ## 1. Persiapan Server -Siapkan: +Install: - Node.js LTS - npm - PostgreSQL - nginx -- certbot / SSL Let’s Encrypt +- certbot -Direktori contoh: +Direktori aplikasi: ```bash /var/www/abelbirdnest-web ``` -## 2. Buat User Khusus Aplikasi - -Jalankan sebagai `root` atau dengan `sudo`: +## 2. Buat User OS Khusus Aplikasi ```bash sudo useradd -r -m -d /var/www/abelbirdnest-web -s /bin/bash abelbirdnest @@ -35,54 +35,44 @@ sudo mkdir -p /var/www/abelbirdnest-web sudo chown -R abelbirdnest:abelbirdnest /var/www/abelbirdnest-web ``` -Catatan: - -- user `abelbirdnest` dipakai khusus untuk menjalankan service aplikasi -- jangan jalankan app production dengan user pribadi atau `root` - -## 3. Clone Repo dari Git - Masuk sebagai user aplikasi: ```bash sudo -u abelbirdnest -H bash cd /var/www/abelbirdnest-web -git clone https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git . ``` -Kalau server butuh autentikasi git internal, siapkan credential sesuai kebijakan server Git Anda. +## 3. Clone Repo -Catatan: - -- perintah di atas hanya benar jika `/var/www/abelbirdnest-web` masih kosong -- jika Anda sudah menjalankan clone biasa dan hasilnya menjadi: - `/var/www/abelbirdnest-web/AbelBirdNest-Stock` - maka lanjutkan semua perintah deploy dari folder itu: +Clone normal: ```bash +git clone https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git cd /var/www/abelbirdnest-web/AbelBirdNest-Stock ``` -- alternatifnya, jika ingin struktur tanpa subfolder tambahan, hapus isi folder tujuan lalu clone ulang dengan titik: +Catatan: + +- panduan ini mengikuti struktur clone normal di atas +- jadi `WorkingDirectory`, `.env.production`, dan semua perintah memakai path: + `/var/www/abelbirdnest-web/AbelBirdNest-Stock` + +## 4. Siapkan Environment Production + +Salin file contoh: ```bash -rm -rf /var/www/abelbirdnest-web/AbelBirdNest-Stock -cd /var/www/abelbirdnest-web -git clone https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git . +cp .env.production.example .env.production ``` -## 4. Environment Production - -Salin `.env.production.example` menjadi `.env.production`, lalu isi nilainya. - -Yang wajib: +Isi minimal: ```env NODE_ENV=production PORT=3007 APP_URL=https://abelbirdnest.id -DATABASE_URL=postgresql://... -AUTH_SECRET=... +DATABASE_URL=postgresql://abelbirdnest_app:password@127.0.0.1:5432/abelbirdnest_prod?schema=public +AUTH_SECRET=ganti-dengan-random-string-panjang AUTH_BOOTSTRAP=false SMTP_HOST=... SMTP_PORT=465 @@ -94,20 +84,13 @@ SMTP_FROM=... Catatan: -- `AUTH_SECRET` harus random panjang. -- `AUTH_BOOTSTRAP=false` wajib untuk production. -- `APP_URL` harus domain production final. +- `AUTH_BOOTSTRAP=false` wajib di production +- akun default dev tidak akan aktif +- `AUTH_SECRET` harus random dan panjang -## 5. Inisialisasi Database PostgreSQL +## 5. Inisialisasi PostgreSQL -Contoh di bawah memakai: - -- database: `abelbirdnest_prod` -- database user: `abelbirdnest_app` -- host: `127.0.0.1` -- port: `5432` - -Masuk ke PostgreSQL sebagai superuser: +Masuk sebagai superuser PostgreSQL: ```bash sudo -u postgres psql @@ -119,26 +102,16 @@ Buat user database: CREATE USER abelbirdnest_app WITH PASSWORD 'ganti-dengan-password-yang-kuat'; ``` -Buat database production: +Buat database: ```sql CREATE DATABASE abelbirdnest_prod OWNER abelbirdnest_app; -``` - -Pastikan owner database benar: - -```sql ALTER DATABASE abelbirdnest_prod OWNER TO abelbirdnest_app; -``` - -Opsional tapi disarankan, kunci privilege default: - -```sql REVOKE ALL ON DATABASE abelbirdnest_prod FROM PUBLIC; GRANT ALL PRIVILEGES ON DATABASE abelbirdnest_prod TO abelbirdnest_app; ``` -Keluar dari `psql`: +Keluar: ```sql \q @@ -150,68 +123,79 @@ Tes koneksi: psql "postgresql://abelbirdnest_app:ganti-dengan-password-yang-kuat@127.0.0.1:5432/abelbirdnest_prod" ``` -Jika koneksi berhasil, pakai URL itu di `.env.production`: - -```env -DATABASE_URL="postgresql://abelbirdnest_app:ganti-dengan-password-yang-kuat@127.0.0.1:5432/abelbirdnest_prod?schema=public" -``` - Catatan: -- untuk `psql`, jangan tambahkan `?schema=public` -- untuk Prisma `DATABASE_URL`, tetap gunakan `?schema=public` +- untuk `psql`, jangan pakai `?schema=public` +- untuk `DATABASE_URL` Prisma, tetap pakai `?schema=public` -## 6. Install Dependency, Database & Migration +## 6. Install Dependency dan Buat Tabel -Repo ini sudah disiapkan memakai migration Prisma. - -Jalankan: +Masih sebagai user `abelbirdnest`: ```bash cd /var/www/abelbirdnest-web/AbelBirdNest-Stock npm install npm run prisma:generate +``` + +Load env production ke shell saat menjalankan command manual: + +```bash +set -a +source .env.production +set +a +``` + +Jalankan migration: + +```bash npm run prisma:migrate:deploy ``` -Kalau perlu isi master awal: +Opsional cek status: ```bash -npm run seed:master +npx prisma migrate status ``` -Data seed yang dibawa: +## 7. Seed Data Awal -- grade -- bank -- currency - -### Khusus Seed Grade - -Seed `grade` membutuhkan file sumber `Grade.xls`. - -Sebelum menjalankan: +Untuk fresh database: ```bash -npm run seed:master +cd /var/www/abelbirdnest-web/AbelBirdNest-Stock +set -a +source .env.production +set +a + +npm run seed:banks +npm run seed:currencies ``` -unggah dulu file `Grade.xls` ke server, misalnya ke: +### Khusus Grade + +`seed:grades` butuh file `Grade.xls`. + +Buat folder data: + +```bash +mkdir -p scripts/data +``` + +Upload file `Grade.xls` ke: ```bash /var/www/abelbirdnest-web/AbelBirdNest-Stock/scripts/data/Grade.xls ``` -Contoh dari laptop lokal: - -```bash -scp "Grade.xls" user@server:/var/www/abelbirdnest-web/AbelBirdNest-Stock/scripts/data/Grade.xls -``` - Lalu jalankan: ```bash cd /var/www/abelbirdnest-web/AbelBirdNest-Stock +set -a +source .env.production +set +a + npm run seed:grades ``` @@ -221,132 +205,210 @@ Alternatif jika file ada di lokasi lain: node scripts/seed-grades-from-xls.mjs /path/ke/Grade.xls ``` -atau: +## 8. Buat User Pertama di PostgreSQL + +Karena `AUTH_BOOTSTRAP=false`, Anda harus buat user login pertama sendiri. + +### 8.1 Generate Hash Password + +Ganti password contoh ini: ```bash -GRADE_XLS_PATH=/path/ke/Grade.xls npm run seed:grades +node -e 'const {randomBytes,scryptSync}=require("crypto"); const p="GantiPasswordKuat123!"; const salt=randomBytes(16).toString("hex"); const derived=scryptSync(p,salt,64).toString("hex"); console.log(`${salt}:${derived}`)' ``` -Urutan pertama kali untuk fresh database: +Simpan output hash-nya. + +### 8.2 Masuk ke PostgreSQL + +```bash +psql "postgresql://abelbirdnest_app:password@127.0.0.1:5432/abelbirdnest_prod" +``` + +### 8.3 Pastikan Role `SYSTEM_ADMIN` Ada + +```sql +INSERT INTO roles (code, name, created_at, updated_at) +VALUES ('SYSTEM_ADMIN', 'System Admin', NOW(), NOW()) +ON CONFLICT (code) DO UPDATE +SET name = EXCLUDED.name, + updated_at = NOW(); +``` + +### 8.4 Buat User Pertama + +Ganti: + +- `Nama Anda` +- `superadmin` +- `superadmin@abelbirdnest.id` +- `HASH_HASIL_LANGKAH_8_1` + +```sql +INSERT INTO users ( + role_id, + name, + username, + email, + email_verified_at, + phone, + password_hash, + status, + created_at, + updated_at +) +SELECT + r.id, + 'Nama Anda', + 'superadmin', + 'superadmin@abelbirdnest.id', + NOW(), + NULL, + 'HASH_HASIL_LANGKAH_8_1', + 'ACTIVE', + NOW(), + NOW() +FROM roles r +WHERE r.code = 'SYSTEM_ADMIN' +ON CONFLICT (email) DO UPDATE +SET role_id = EXCLUDED.role_id, + name = EXCLUDED.name, + username = EXCLUDED.username, + email_verified_at = EXCLUDED.email_verified_at, + password_hash = EXCLUDED.password_hash, + status = EXCLUDED.status, + updated_at = NOW(); +``` + +Verifikasi: + +```sql +SELECT u.id, u.name, u.username, u.email, u.status, r.code AS role +FROM users u +JOIN roles r ON r.id = u.role_id +WHERE u.email = 'superadmin@abelbirdnest.id'; +``` + +## 9. Build Production ```bash cd /var/www/abelbirdnest-web/AbelBirdNest-Stock -npm install -npm run prisma:generate -npm run prisma:migrate:deploy -npm run seed:banks -npm run seed:currencies -npm run seed:grades -``` +set -a +source .env.production +set +a -Catatan: - -- `prisma:migrate:deploy` akan membuat seluruh tabel dari migration yang ada di repo -- `seed:banks` dan `seed:currencies` bisa langsung dijalankan -- `seed:grades` butuh file `Grade.xls` lebih dulu -- user login production tetap harus dibuat terpisah, jangan mengandalkan akun dev/default - -## 7. Build Production - -```bash -cd /var/www/abelbirdnest-web/AbelBirdNest-Stock npm run build ``` -## 8. Jalankan App di Port 3007 +## 10. Jalankan App dengan systemd -Manual: - -```bash -PORT=3007 npm run start -``` - -Atau gunakan `systemd` dari: +File service repo sudah disiapkan untuk struktur subfolder ini: ```bash deploy/systemd/abelbirdnest-web.service ``` -Contoh setup: +Pasang: ```bash -sudo cp deploy/systemd/abelbirdnest-web.service /etc/systemd/system/ +sudo cp /var/www/abelbirdnest-web/AbelBirdNest-Stock/deploy/systemd/abelbirdnest-web.service /etc/systemd/system/ +sudo chown -R abelbirdnest:abelbirdnest /var/www/abelbirdnest-web/AbelBirdNest-Stock sudo systemctl daemon-reload sudo systemctl enable abelbirdnest-web -sudo systemctl start abelbirdnest-web +sudo systemctl restart abelbirdnest-web sudo systemctl status abelbirdnest-web ``` -Autostart saat server restart terjadi karena service di-`enable`. - -Untuk verifikasi: +Verifikasi port: ```bash -sudo systemctl is-enabled abelbirdnest-web +ss -ltnp | grep 3007 +curl http://127.0.0.1:3007/api/v1/health ``` -## 9. Reverse Proxy Nginx - -Gunakan file: +Kalau service gagal, cek: ```bash -deploy/nginx/abelbirdnest.id.conf +sudo systemctl cat abelbirdnest-web +sudo journalctl -u abelbirdnest-web -n 100 --no-pager +which npm +``` + +## 11. Pasang Nginx Tahap 1: HTTP Dulu + +Jangan langsung pakai config HTTPS sebelum sertifikat ada. + +Pakai file ini dulu: + +```bash +deploy/nginx/abelbirdnest.id.http.conf ``` Pasang: ```bash -sudo cp deploy/nginx/abelbirdnest.id.conf /etc/nginx/sites-available/abelbirdnest.id.conf -sudo ln -s /etc/nginx/sites-available/abelbirdnest.id.conf /etc/nginx/sites-enabled/abelbirdnest.id.conf +sudo cp /var/www/abelbirdnest-web/AbelBirdNest-Stock/deploy/nginx/abelbirdnest.id.http.conf /etc/nginx/sites-available/abelbirdnest.id.conf +sudo ln -sf /etc/nginx/sites-available/abelbirdnest.id.conf /etc/nginx/sites-enabled/abelbirdnest.id.conf sudo nginx -t sudo systemctl reload nginx ``` -## 10. Health Check - -Endpoint health: +Tes: ```bash -GET /api/v1/health +curl http://abelbirdnest.id/api/v1/health ``` -Contoh: +## 12. Buat Sertifikat SSL + +Setelah HTTP sudah hidup: + +```bash +sudo certbot --nginx -d abelbirdnest.id -d www.abelbirdnest.id +``` + +## 13. Ganti ke Config HTTPS Final + +Setelah sertifikat berhasil dibuat, baru pakai config final: + +```bash +sudo cp /var/www/abelbirdnest-web/AbelBirdNest-Stock/deploy/nginx/abelbirdnest.id.conf /etc/nginx/sites-available/abelbirdnest.id.conf +sudo nginx -t +sudo systemctl reload nginx +``` + +Verifikasi: ```bash curl https://abelbirdnest.id/api/v1/health ``` -## 11. Update Deployment Berikutnya - -Jika aplikasi sudah live dan ada update dari git: +## 14. Update Deployment Berikutnya ```bash cd /var/www/abelbirdnest-web/AbelBirdNest-Stock git pull origin main + +set -a +source .env.production +set +a + npm install +npm run prisma:generate npm run prisma:migrate:deploy npm run build sudo systemctl restart abelbirdnest-web +sudo systemctl status abelbirdnest-web ``` -Jika branch utama nanti bukan `main`, sesuaikan perintah `git pull`. +## 15. Smoke Test Setelah Live -## 12. Checklist Go-Live +Cek minimal: -- `AUTH_BOOTSTRAP=false` -- `AUTH_SECRET` sudah production-grade -- `APP_URL=https://abelbirdnest.id` -- SSL aktif -- database backup aktif -- `npm run build` lulus -- `npm run prisma:migrate:deploy` lulus -- `npm run seed:master` selesai jika dibutuhkan -- login, reset password, dan email verifikasi sudah dites -- create purchase, receipt, lot, sale sudah dites - -## 13. Catatan Penting - -- Jangan pakai `npm run db:push` untuk production. -- Jangan pakai akun default development. -- Jangan simpan `.env.production` di repo. -- Pastikan ownership file tetap `abelbirdnest:abelbirdnest`. +- login dengan user `SYSTEM_ADMIN` +- dashboard terbuka +- health check OK +- buat master `bank`, `currency`, `grade` tampil +- buat transaksi sederhana +- logout/login ulang +- email reset/verifikasi jika SMTP sudah aktif diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx index 7070619..1580c6f 100644 --- a/src/components/layout/app-shell.tsx +++ b/src/components/layout/app-shell.tsx @@ -31,6 +31,9 @@ export async function AppShell({
{children}
+
+ © 2026 AbelBirdnest +
diff --git a/src/features/auth/components/login-client.tsx b/src/features/auth/components/login-client.tsx index 4b6a0cc..bace4d5 100644 --- a/src/features/auth/components/login-client.tsx +++ b/src/features/auth/components/login-client.tsx @@ -6,28 +6,20 @@ import { FormEvent, useState } from "react"; import { AppLogo } from "@/components/branding/app-logo"; import { useLocale } from "@/components/providers/locale-provider"; -import { defaultAuthAccounts } from "@/features/auth/lib/default-accounts"; export function LoginClient() { const { dict } = useLocale(); const router = useRouter(); const searchParams = useSearchParams(); - const [identity, setIdentity] = useState("admin@abelbirdnest.local"); - const [password, setPassword] = useState("admin123"); + const [identity, setIdentity] = useState(""); + const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); - const [helperEmail, setHelperEmail] = useState("admin@abelbirdnest.local"); + const [helperEmail, setHelperEmail] = useState(""); const [helperSubmitting, setHelperSubmitting] = useState<"reset" | "verify" | null>(null); const [helperMessage, setHelperMessage] = useState(null); - function applyAccount(identityValue: string, passwordValue: string) { - setIdentity(identityValue); - setPassword(passwordValue); - setHelperEmail(identityValue); - setError(null); - } - async function submitHelper(type: "reset" | "verify") { setHelperSubmitting(type); setHelperMessage(null); @@ -246,31 +238,6 @@ export function LoginClient() { {dict.login.privacy} {dict.login.terms} -
-

{dict.login.devAccounts}

-
- {defaultAuthAccounts.map((account) => ( - - ))} -
-

- {dict.login.autoFillHint} -

-
@@ -278,7 +245,7 @@ export function LoginClient() {
v2.4.0-pro - © 2024 AbelBirdnest Stock + © 2026 AbelBirdnest
);