Harden login and refresh production deploy guide
This commit is contained in:
26
deploy/nginx/abelbirdnest.id.http.conf
Normal file
26
deploy/nginx/abelbirdnest.id.http.conf
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,10 +4,10 @@ After=network.target postgresql.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
WorkingDirectory=/var/www/abelbirdnest-web
|
WorkingDirectory=/var/www/abelbirdnest-web/AbelBirdNest-Stock
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
Environment=PORT=3007
|
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
|
ExecStart=/usr/bin/npm run start
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Deploy Production
|
# Deploy Production
|
||||||
|
|
||||||
Dokumen ini menyiapkan deploy production untuk:
|
Panduan ini untuk deploy production dengan asumsi:
|
||||||
|
|
||||||
- domain `abelbirdnest.id`
|
- domain `abelbirdnest.id`
|
||||||
- reverse proxy `nginx`
|
- reverse proxy `nginx`
|
||||||
@ -8,26 +8,26 @@ Dokumen ini menyiapkan deploy production untuk:
|
|||||||
- database `PostgreSQL`
|
- database `PostgreSQL`
|
||||||
- source code dari git `https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git`
|
- source code dari git `https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git`
|
||||||
- user service khusus `abelbirdnest`
|
- user service khusus `abelbirdnest`
|
||||||
|
- repo berada di:
|
||||||
|
`/var/www/abelbirdnest-web/AbelBirdNest-Stock`
|
||||||
|
|
||||||
## 1. Persiapan Server
|
## 1. Persiapan Server
|
||||||
|
|
||||||
Siapkan:
|
Install:
|
||||||
|
|
||||||
- Node.js LTS
|
- Node.js LTS
|
||||||
- npm
|
- npm
|
||||||
- PostgreSQL
|
- PostgreSQL
|
||||||
- nginx
|
- nginx
|
||||||
- certbot / SSL Let’s Encrypt
|
- certbot
|
||||||
|
|
||||||
Direktori contoh:
|
Direktori aplikasi:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/var/www/abelbirdnest-web
|
/var/www/abelbirdnest-web
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Buat User Khusus Aplikasi
|
## 2. Buat User OS Khusus Aplikasi
|
||||||
|
|
||||||
Jalankan sebagai `root` atau dengan `sudo`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo useradd -r -m -d /var/www/abelbirdnest-web -s /bin/bash abelbirdnest
|
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
|
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:
|
Masuk sebagai user aplikasi:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo -u abelbirdnest -H bash
|
sudo -u abelbirdnest -H bash
|
||||||
cd /var/www/abelbirdnest-web
|
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:
|
Clone normal:
|
||||||
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git clone https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git
|
||||||
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
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
|
```bash
|
||||||
rm -rf /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
cp .env.production.example .env.production
|
||||||
cd /var/www/abelbirdnest-web
|
|
||||||
git clone https://git.iptek.co/wirabasalamah/AbelBirdNest-Stock.git .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. Environment Production
|
Isi minimal:
|
||||||
|
|
||||||
Salin `.env.production.example` menjadi `.env.production`, lalu isi nilainya.
|
|
||||||
|
|
||||||
Yang wajib:
|
|
||||||
|
|
||||||
```env
|
```env
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
PORT=3007
|
PORT=3007
|
||||||
APP_URL=https://abelbirdnest.id
|
APP_URL=https://abelbirdnest.id
|
||||||
DATABASE_URL=postgresql://...
|
DATABASE_URL=postgresql://abelbirdnest_app:password@127.0.0.1:5432/abelbirdnest_prod?schema=public
|
||||||
AUTH_SECRET=...
|
AUTH_SECRET=ganti-dengan-random-string-panjang
|
||||||
AUTH_BOOTSTRAP=false
|
AUTH_BOOTSTRAP=false
|
||||||
SMTP_HOST=...
|
SMTP_HOST=...
|
||||||
SMTP_PORT=465
|
SMTP_PORT=465
|
||||||
@ -94,20 +84,13 @@ SMTP_FROM=...
|
|||||||
|
|
||||||
Catatan:
|
Catatan:
|
||||||
|
|
||||||
- `AUTH_SECRET` harus random panjang.
|
- `AUTH_BOOTSTRAP=false` wajib di production
|
||||||
- `AUTH_BOOTSTRAP=false` wajib untuk production.
|
- akun default dev tidak akan aktif
|
||||||
- `APP_URL` harus domain production final.
|
- `AUTH_SECRET` harus random dan panjang
|
||||||
|
|
||||||
## 5. Inisialisasi Database PostgreSQL
|
## 5. Inisialisasi PostgreSQL
|
||||||
|
|
||||||
Contoh di bawah memakai:
|
Masuk sebagai superuser PostgreSQL:
|
||||||
|
|
||||||
- database: `abelbirdnest_prod`
|
|
||||||
- database user: `abelbirdnest_app`
|
|
||||||
- host: `127.0.0.1`
|
|
||||||
- port: `5432`
|
|
||||||
|
|
||||||
Masuk ke PostgreSQL sebagai superuser:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo -u postgres psql
|
sudo -u postgres psql
|
||||||
@ -119,26 +102,16 @@ Buat user database:
|
|||||||
CREATE USER abelbirdnest_app WITH PASSWORD 'ganti-dengan-password-yang-kuat';
|
CREATE USER abelbirdnest_app WITH PASSWORD 'ganti-dengan-password-yang-kuat';
|
||||||
```
|
```
|
||||||
|
|
||||||
Buat database production:
|
Buat database:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE DATABASE abelbirdnest_prod OWNER abelbirdnest_app;
|
CREATE DATABASE abelbirdnest_prod OWNER abelbirdnest_app;
|
||||||
```
|
|
||||||
|
|
||||||
Pastikan owner database benar:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER DATABASE abelbirdnest_prod OWNER TO abelbirdnest_app;
|
ALTER DATABASE abelbirdnest_prod OWNER TO abelbirdnest_app;
|
||||||
```
|
|
||||||
|
|
||||||
Opsional tapi disarankan, kunci privilege default:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
REVOKE ALL ON DATABASE abelbirdnest_prod FROM PUBLIC;
|
REVOKE ALL ON DATABASE abelbirdnest_prod FROM PUBLIC;
|
||||||
GRANT ALL PRIVILEGES ON DATABASE abelbirdnest_prod TO abelbirdnest_app;
|
GRANT ALL PRIVILEGES ON DATABASE abelbirdnest_prod TO abelbirdnest_app;
|
||||||
```
|
```
|
||||||
|
|
||||||
Keluar dari `psql`:
|
Keluar:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
\q
|
\q
|
||||||
@ -150,68 +123,79 @@ Tes koneksi:
|
|||||||
psql "postgresql://abelbirdnest_app:ganti-dengan-password-yang-kuat@127.0.0.1:5432/abelbirdnest_prod"
|
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:
|
Catatan:
|
||||||
|
|
||||||
- untuk `psql`, jangan tambahkan `?schema=public`
|
- untuk `psql`, jangan pakai `?schema=public`
|
||||||
- untuk Prisma `DATABASE_URL`, tetap gunakan `?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.
|
Masih sebagai user `abelbirdnest`:
|
||||||
|
|
||||||
Jalankan:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
||||||
npm install
|
npm install
|
||||||
npm run prisma:generate
|
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
|
npm run prisma:migrate:deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
Kalau perlu isi master awal:
|
Opsional cek status:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run seed:master
|
npx prisma migrate status
|
||||||
```
|
```
|
||||||
|
|
||||||
Data seed yang dibawa:
|
## 7. Seed Data Awal
|
||||||
|
|
||||||
- grade
|
Untuk fresh database:
|
||||||
- bank
|
|
||||||
- currency
|
|
||||||
|
|
||||||
### Khusus Seed Grade
|
|
||||||
|
|
||||||
Seed `grade` membutuhkan file sumber `Grade.xls`.
|
|
||||||
|
|
||||||
Sebelum menjalankan:
|
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
/var/www/abelbirdnest-web/AbelBirdNest-Stock/scripts/data/Grade.xls
|
/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:
|
Lalu jalankan:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
||||||
|
set -a
|
||||||
|
source .env.production
|
||||||
|
set +a
|
||||||
|
|
||||||
npm run seed:grades
|
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
|
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
|
```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
|
```bash
|
||||||
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
||||||
npm install
|
set -a
|
||||||
npm run prisma:generate
|
source .env.production
|
||||||
npm run prisma:migrate:deploy
|
set +a
|
||||||
npm run seed:banks
|
|
||||||
npm run seed:currencies
|
|
||||||
npm run seed:grades
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8. Jalankan App di Port 3007
|
## 10. Jalankan App dengan systemd
|
||||||
|
|
||||||
Manual:
|
File service repo sudah disiapkan untuk struktur subfolder ini:
|
||||||
|
|
||||||
```bash
|
|
||||||
PORT=3007 npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
Atau gunakan `systemd` dari:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deploy/systemd/abelbirdnest-web.service
|
deploy/systemd/abelbirdnest-web.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Contoh setup:
|
Pasang:
|
||||||
|
|
||||||
```bash
|
```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 daemon-reload
|
||||||
sudo systemctl enable abelbirdnest-web
|
sudo systemctl enable abelbirdnest-web
|
||||||
sudo systemctl start abelbirdnest-web
|
sudo systemctl restart abelbirdnest-web
|
||||||
sudo systemctl status abelbirdnest-web
|
sudo systemctl status abelbirdnest-web
|
||||||
```
|
```
|
||||||
|
|
||||||
Autostart saat server restart terjadi karena service di-`enable`.
|
Verifikasi port:
|
||||||
|
|
||||||
Untuk verifikasi:
|
|
||||||
|
|
||||||
```bash
|
```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
|
Kalau service gagal, cek:
|
||||||
|
|
||||||
Gunakan file:
|
|
||||||
|
|
||||||
```bash
|
```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:
|
Pasang:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo cp deploy/nginx/abelbirdnest.id.conf /etc/nginx/sites-available/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 -s /etc/nginx/sites-available/abelbirdnest.id.conf /etc/nginx/sites-enabled/abelbirdnest.id.conf
|
sudo ln -sf /etc/nginx/sites-available/abelbirdnest.id.conf /etc/nginx/sites-enabled/abelbirdnest.id.conf
|
||||||
sudo nginx -t
|
sudo nginx -t
|
||||||
sudo systemctl reload nginx
|
sudo systemctl reload nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
## 10. Health Check
|
Tes:
|
||||||
|
|
||||||
Endpoint health:
|
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
curl https://abelbirdnest.id/api/v1/health
|
curl https://abelbirdnest.id/api/v1/health
|
||||||
```
|
```
|
||||||
|
|
||||||
## 11. Update Deployment Berikutnya
|
## 14. Update Deployment Berikutnya
|
||||||
|
|
||||||
Jika aplikasi sudah live dan ada update dari git:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
cd /var/www/abelbirdnest-web/AbelBirdNest-Stock
|
||||||
git pull origin main
|
git pull origin main
|
||||||
|
|
||||||
|
set -a
|
||||||
|
source .env.production
|
||||||
|
set +a
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
|
npm run prisma:generate
|
||||||
npm run prisma:migrate:deploy
|
npm run prisma:migrate:deploy
|
||||||
npm run build
|
npm run build
|
||||||
sudo systemctl restart abelbirdnest-web
|
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`
|
- login dengan user `SYSTEM_ADMIN`
|
||||||
- `AUTH_SECRET` sudah production-grade
|
- dashboard terbuka
|
||||||
- `APP_URL=https://abelbirdnest.id`
|
- health check OK
|
||||||
- SSL aktif
|
- buat master `bank`, `currency`, `grade` tampil
|
||||||
- database backup aktif
|
- buat transaksi sederhana
|
||||||
- `npm run build` lulus
|
- logout/login ulang
|
||||||
- `npm run prisma:migrate:deploy` lulus
|
- email reset/verifikasi jika SMTP sudah aktif
|
||||||
- `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`.
|
|
||||||
|
|||||||
@ -31,6 +31,9 @@ export async function AppShell({
|
|||||||
<MobileNav pathname={pathname} userRole={user.role} />
|
<MobileNav pathname={pathname} userRole={user.role} />
|
||||||
<Topbar title={copy.title} description={copy.description} user={user} />
|
<Topbar title={copy.title} description={copy.description} user={user} />
|
||||||
<div className="flex-1 p-6">{children}</div>
|
<div className="flex-1 p-6">{children}</div>
|
||||||
|
<footer className="border-t border-line/70 px-6 py-4 text-center text-sm text-slate-500">
|
||||||
|
© 2026 AbelBirdnest
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,28 +6,20 @@ import { FormEvent, useState } from "react";
|
|||||||
|
|
||||||
import { AppLogo } from "@/components/branding/app-logo";
|
import { AppLogo } from "@/components/branding/app-logo";
|
||||||
import { useLocale } from "@/components/providers/locale-provider";
|
import { useLocale } from "@/components/providers/locale-provider";
|
||||||
import { defaultAuthAccounts } from "@/features/auth/lib/default-accounts";
|
|
||||||
|
|
||||||
export function LoginClient() {
|
export function LoginClient() {
|
||||||
const { dict } = useLocale();
|
const { dict } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [identity, setIdentity] = useState("admin@abelbirdnest.local");
|
const [identity, setIdentity] = useState("");
|
||||||
const [password, setPassword] = useState("admin123");
|
const [password, setPassword] = useState("");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [helperEmail, setHelperEmail] = useState("admin@abelbirdnest.local");
|
const [helperEmail, setHelperEmail] = useState("");
|
||||||
const [helperSubmitting, setHelperSubmitting] = useState<"reset" | "verify" | null>(null);
|
const [helperSubmitting, setHelperSubmitting] = useState<"reset" | "verify" | null>(null);
|
||||||
const [helperMessage, setHelperMessage] = useState<string | null>(null);
|
const [helperMessage, setHelperMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
function applyAccount(identityValue: string, passwordValue: string) {
|
|
||||||
setIdentity(identityValue);
|
|
||||||
setPassword(passwordValue);
|
|
||||||
setHelperEmail(identityValue);
|
|
||||||
setError(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitHelper(type: "reset" | "verify") {
|
async function submitHelper(type: "reset" | "verify") {
|
||||||
setHelperSubmitting(type);
|
setHelperSubmitting(type);
|
||||||
setHelperMessage(null);
|
setHelperMessage(null);
|
||||||
@ -246,31 +238,6 @@ export function LoginClient() {
|
|||||||
<span>{dict.login.privacy}</span>
|
<span>{dict.login.privacy}</span>
|
||||||
<span>{dict.login.terms}</span>
|
<span>{dict.login.terms}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 rounded-lg border border-blue-200 bg-blue-50 px-4 py-4 text-left text-[13px] leading-6 text-slate-600">
|
|
||||||
<p className="font-semibold text-slate-700">{dict.login.devAccounts}</p>
|
|
||||||
<div className="mt-3 space-y-3">
|
|
||||||
{defaultAuthAccounts.map((account) => (
|
|
||||||
<button
|
|
||||||
key={account.roleCode}
|
|
||||||
type="button"
|
|
||||||
onClick={() => applyAccount(account.email, account.password)}
|
|
||||||
className="block w-full rounded-lg border border-blue-200 bg-white px-3 py-3 text-left transition hover:border-moss/40 hover:bg-moss/5"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
|
||||||
<span className="font-semibold text-ink">{account.roleLabel}</span>
|
|
||||||
<span className="text-[11px] font-bold uppercase tracking-[0.08em] text-slate-500">
|
|
||||||
{account.username}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 text-[12px] text-slate-600">{account.email}</div>
|
|
||||||
<div className="text-[12px] text-slate-600">Password: <span className="font-semibold text-ink">{account.password}</span></div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<p className="mt-3 text-[12px] text-slate-500">
|
|
||||||
{dict.login.autoFillHint}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -278,7 +245,7 @@ export function LoginClient() {
|
|||||||
|
|
||||||
<div className="pointer-events-none fixed bottom-0 left-0 flex w-full justify-between px-4 py-3 text-[11px] font-bold uppercase tracking-[0.08em] text-slate-400">
|
<div className="pointer-events-none fixed bottom-0 left-0 flex w-full justify-between px-4 py-3 text-[11px] font-bold uppercase tracking-[0.08em] text-slate-400">
|
||||||
<span>v2.4.0-pro</span>
|
<span>v2.4.0-pro</span>
|
||||||
<span>© 2024 AbelBirdnest Stock</span>
|
<span>© 2026 AbelBirdnest</span>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user