# Requirement & Rekomendasi — Ollama + Pencarian Kontekstual NAS **Tanggal:** 2026-06-15 **Use case:** Pencarian proyek/file di share QNAP berdasarkan konteks natural language **Contoh query:** *"Carikan projek Pertamina → lokasi folder dan kapan dibuat"* **Integrasi target:** geonet-console + PostgreSQL (`10.100.1.25`) + Ollama inference host terpisah --- ## Ringkasan keputusan | Pertanyaan | Jawaban | |------------|---------| | Apakah Ollama bisa langsung scan `\\10.100.1.10\project`? | **Tidak** — perlu **indexer + database** | | Bisa multi-folder (project, Marketing, dll.)? | **Ya** — via env **indexer**, bukan env Ollama | | Host Ollama POC & produksi CPU | **VM `10.100.1.26` (aiopr)** — Ubuntu 22.04, dedicated | | Host Ollama GPU (target) | Migrasi ke **Windows 11 + GPU** (~6 bulan), ganti `OLLAMA_BASE_URL` | | Model Ubuntu → Windows portable? | **Ya** — copy folder `models/` atau `ollama pull` ulang | | Indeks RAG ikut pindah ke PC Windows? | **Tidak perlu** — tetap di PostgreSQL `.25` | | Tanya license code / tanggal expired dari PDF sertifikat? | **Ya** — via **License extraction** + `license_registry` (lihat section khusus) | --- ## Arsitektur ``` ┌─────────────────────────────────────────────────────────────────┐ │ User → geonet-console (chat UI) / API v1 │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Project Search Service (Laravel / worker) │ │ - Terima query natural language │ │ - Filter scope source (project, marketing, …) │ │ - Audit log setiap pencarian │ └──────────────┬──────────────────────────────┬───────────────────┘ │ │ ▼ ▼ ┌──────────────────────────┐ ┌─────────────────────────────────┐ │ PostgreSQL 10.100.1.25 │ │ Ollama 10.100.1.26 (aiopr) │ │ - project_index │ │ - qwen2.5 (chat) │ │ - project_chunks │ │ - nomic-embed-text (embedding) │ │ - license_registry │ │ OLLAMA_BASE_URL → .26:11434 │ │ - pgvector │ │ │ └──────────────────────────┘ └─────────────────────────────────┘ ▲ │ ┌──────────────────────────┐ │ Indexer Worker (.26 POC)│ │ - Mount SMB QNAP │ │ - Crawl incremental │ │ - Ekstrak teks dokumen │ │ - Generate embedding │ └──────────────┬─────────────┘ │ ▼ ┌──────────────────────────┐ │ QNAP TS-932X 10.100.1.10│ │ SMB shares │ └──────────────────────────┘ ``` **Prinsip:** Ollama = lapisan bahasa alami. **Indexer + PostgreSQL** = otak pencarian. NAS = storage saja. --- ## Sumber data NAS (QNAP) ### Share utama — use case project search | SMB path | Path internal NAS | Catatan | |----------|-------------------|---------| | `\\10.100.1.10\project` | `/share/CACHEDEV3_DATA/project` | Struktur per tahun `2019`–`2026` | | Ukuran (audit 2026-06-15) | ~**2,6 TB**, ~**1,4 juta file** | Indeks incremental wajib | ### Share tambahan (multi-source) | SMB path | Path internal NAS | |----------|-------------------| | `\\10.100.1.10\Marketing` | `/share/CACHEDEV2_DATA/Marketing` | | `\\10.100.1.10\Marketing\Nanda\DATA SALES` | subfolder Marketing | | `\\10.100.1.10\GIS` | `/share/CACHEDEV2_DATA/GIS` | | `\\10.100.1.10\sales` | `/share/CACHEDEV3_DATA/sales` | | `\\10.100.1.10\development` | `/share/CACHEDEV3_DATA/development` | Lihat symlink lengkap: SSH `admin@10.100.1.10` → `ls -la /share/` ### Contoh folder Pertamina (verifikasi 2026-06-15) - `project\2020\Pertamina EP` - `project\2021\Pertamina EP`, `Pertamina Hulu Rokan (PHR)` - `project\2023\PT. Pertamina Geothermal Energy Tbk`, PHKT, PHE Holding, dll. - `project\2025\PT. Pertamina Geothermal Energy Tbk`, `PERTAMINA EP` - `project\2026\PT. Pertamina Geothermal Energy Tbk` --- ## Fase implementasi ### Fase 1 — Metadata search (1–2 minggu) **Scope:** path, nama folder/klien, tahun (dari parent folder), `mtime`/`ctime`, owner. | Item | Requirement | |------|-------------| | Crawler | Scan SMB, simpan metadata ke PostgreSQL | | Pencarian | Keyword + filter tahun/source | | LLM | Ollama format jawaban dari hasil terstruktur (bukan tebak path) | | Jawaban contoh | Lokasi UNC + tahun folder + tanggal modifikasi | **Catatan tanggal "kapan dibuat":** birth time di QNAP sering tidak andal. Gunakan: 1. Tahun dari path (`/2023/...`) 2. `mtime` folder 3. Tanggal file tertua di dalam folder (opsional) ### Fase 2 — RAG penuh (produksi) **Scope:** ekstrak teks `.docx`, `.pdf`, `.xlsx`, `.msg` → chunk → embedding → semantic search. | Item | Requirement | |------|-------------| | Embedding model | `nomic-embed-text` via Ollama | | Vector store | PostgreSQL + **pgvector** di `10.100.1.25` | | Chat model | `qwen2.5:7b` (CPU) / `qwen2.5:14b` (GPU) | | Chunk strategy | Per dokumen, max ~512–1024 token, metadata source_id | ### Fase 2b — License / Certificate extraction (prioritas tinggi) **Scope:** parse terstruktur dokumen sertifikat & license dari folder proyek — akurat untuk kode license, pemilik, dan tanggal expired. Lihat section **[License / Certificate extraction](#license--certificate-extraction)** di bawah. --- ## License / Certificate extraction ### Use case User bertanya dalam bahasa natural; sistem jawab dari **data terstruktur** (bukan tebakan LLM): | Pertanyaan contoh | Jawaban yang diharapkan | |-------------------|-------------------------| | *"License `AzqehoVMExxV24ww` milik siapa?"* | PT. Kalimantan Prima Persada | | *"Kapan expired `AzqehoVMExxV24ww`?"* | End date **09/06/2027** | | *"Sertifikat Global Mapper PO A17944 untuk siapa?"* | PT. Kalimantan Prima Persada, license `AzqehoVMExxV24ww` | | *"Certificate ESRI No 583582?"* | Pertamina Hulu Kalimantan Timur | ### File verifikasi (2026-06-15) **Path SMB:** `\\10.100.1.10\project\2026\PT Kalimantan Prima Persada #AzqehoVMExxV24ww\Sertifikat Software Global Mapper PT Kalimantan Prima Persada - PO A17944.pdf` **Hasil ekstrak teks (template Geonet / Blue Marble):** | Field | Nilai | |-------|--------| | Template | `geonet_global_mapper_v1` | | End User | PT. Kalimantan Prima Persada | | License Number | AzqehoVMExxV24ww | | Produk | Global Mapper V27 Network License (qty 4) | | Produk | Global Mapper Pro add-on V27 Network License (qty 4) | | Start Date | 10/06/2026 | | End Date | **09/06/2027** | | PO | A17944 (dari nama file) | | Penerbit | PT. Geonet Infomedia | PDF ini **berbasis teks** — ekstraksi `pypdf`/`pdfplumber` cukup, tanpa OCR. **Metadata folder:** nama folder mengandung `#AzqehoVMExxV24ww` → Fase 1 bisa link license ke klien tanpa buka PDF; **end date** tetap butuh parse dokumen. ### Prinsip desain ``` PDF/MSG/DOCX → Deteksi template → Parser terstruktur → license_registry ↘ project_chunks (RAG opsional) ↓ User query → Lookup license_registry (exact) → Ollama format jawaban natural ``` | Layer | Fungsi | |-------|--------| | **Template detector** | Cocokkan header/frasa khas per vendor (ESRI, Geonet GM, dll.) | | **Structured parser** | Ekstrak field: `license_code`, `client_name`, `start_date`, `end_date`, … | | **`license_registry`** | Sumber kebenaran untuk kode license & tanggal — **jangan andalkan LLM saja** | | **Ollama** | Hanya merangkai jawaban dari baris DB yang match | | **RAG (`project_chunks`)** | Pelengkap untuk pertanyaan naratif di luar field terstruktur | **Aturan akurasi:** untuk `license_code`, `end_date`, `client_name` → jawab **hanya** dari `license_registry`. Jika tidak ada di DB, jawab *"tidak ditemukan di indeks"* — jangan hallucinate. ### Alur indexer — dokumen license 1. Crawl file dengan pola nama/path (lihat [Prioritas indeks](#prioritas-indeks-file-license)). 2. Ekstrak teks: - PDF teks → `pypdf` / `pdfplumber` - PDF scan / JPG / JPEG → **OCR** (Tesseract `ind+eng`) - `.msg` → `extract-msg` / Outlook parser - `.docx` → `python-docx` 3. `detect_template(extracted_text, filename)` → `template_id` 4. `parse(template_id, text)` → satu atau banyak baris `license_registry` 5. Simpan `raw_excerpt` + `confidence_score` untuk audit 6. (Opsional) chunk teks ke `project_chunks` untuk RAG ### Skema `license_registry` ```sql -- Enum-like: gunakan VARCHAR + CHECK atau tabel license_vendors CREATE TABLE license_vendors ( id VARCHAR(32) PRIMARY KEY, -- 'esri', 'blue_marble', 'mapinfo', ... display_name VARCHAR(128) NOT NULL ); INSERT INTO license_vendors (id, display_name) VALUES ('esri', 'Esri / ESRI Indonesia'), ('blue_marble', 'Blue Marble Geographics (Global Mapper)'), ('geonet_reseller', 'PT. Geonet Infomedia (Certificate of Authenticity)'), ('mapinfo', 'Precisely MapInfo'), ('safe_software', 'Safe Software (FME)'), ('other', 'Lainnya / belum diklasifikasi'); CREATE TABLE license_document_templates ( id VARCHAR(64) PRIMARY KEY, -- 'geonet_global_mapper_v1', 'esri_certificate_id_v1', ... vendor_id VARCHAR(32) NOT NULL REFERENCES license_vendors(id), display_name VARCHAR(255) NOT NULL, detection_rules JSONB NOT NULL, -- frasa/header regex untuk auto-detect parser_class VARCHAR(128) NOT NULL, -- FQCN parser di worker is_active BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE license_registry ( id BIGSERIAL PRIMARY KEY, source_id VARCHAR(64) NOT NULL, -- sama dengan project_index template_id VARCHAR(64) REFERENCES license_document_templates(id), vendor_id VARCHAR(32) NOT NULL REFERENCES license_vendors(id), -- Identitas license (index utama pencarian) license_code VARCHAR(128), -- AzqehoVMExxV24ww, EUN#, SA#, dll. license_code_norm VARCHAR(128) GENERATED ALWAYS AS (upper(trim(license_code))) STORED, esri_auth_number VARCHAR(64), -- nomor certificate ESRI jika ada (583582) order_number VARCHAR(64), -- PO A17944, Order # 216760-AXGWF5T5 product_name VARCHAR(255) NOT NULL, product_version VARCHAR(64), -- V27, 10.9.1, ArcGIS 11.0 license_type VARCHAR(64), -- network, single_use, concurrent, maintenance quantity INT, -- Pemilik / end user client_name VARCHAR(255) NOT NULL, client_email VARCHAR(255), client_address TEXT, -- Periode start_date DATE, end_date DATE, -- maintenance end / expired date_format_hint VARCHAR(32) DEFAULT 'D/M/Y', -- format asli di dokumen -- Lokasi file sumber relative_path TEXT NOT NULL, full_smb_path TEXT NOT NULL, file_name VARCHAR(512) NOT NULL, file_ext VARCHAR(16), file_mtime TIMESTAMPTZ, file_sha256 CHAR(64), -- deduplikasi re-index -- Audit ekstraksi raw_excerpt TEXT, -- cuplikan teks yang diparse (max ~4 KB) confidence_score SMALLINT CHECK (confidence_score BETWEEN 0 AND 100), parse_warnings JSONB, -- field yang gagal / ambigu indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), parser_version VARCHAR(32) NOT NULL, UNIQUE (source_id, full_smb_path, license_code, product_name) ); -- Pencarian cepat license code (pertanyaan paling umum) CREATE INDEX idx_license_registry_code ON license_registry (license_code_norm); CREATE INDEX idx_license_registry_esri_auth ON license_registry (esri_auth_number) WHERE esri_auth_number IS NOT NULL; CREATE INDEX idx_license_registry_client ON license_registry USING gin (client_name gin_trgm_ops); CREATE INDEX idx_license_registry_end_date ON license_registry (end_date); CREATE INDEX idx_license_registry_order ON license_registry (order_number) WHERE order_number IS NOT NULL; -- View: license akan expired dalam 90 hari CREATE VIEW license_expiring_soon AS SELECT * FROM license_registry WHERE end_date IS NOT NULL AND end_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '90 days' ORDER BY end_date; ``` **Normalisasi `license_code`:** simpan asli + `license_code_norm` (uppercase, trim). Pertanyaan user case-insensitive. **Satu PDF → banyak baris:** template Global Mapper Geonet menghasilkan 2+ produk dengan license code sama — insert satu baris per produk (lihat contoh verifikasi). ### Daftar template dokumen Template di bawah ini ditemukan di share `\\10.100.1.10\project` (sampling 2019–2026). Implementasi parser per prioritas bisnis. #### Prioritas P1 — implementasi pertama | `template_id` | Vendor | Pola file / deteksi | Field utama | |---------------|--------|---------------------|-------------| | `geonet_global_mapper_v1` | Geonet / Blue Marble | `Certificate of Authenticity`, `PT. Geonet Infomedia`, tabel `License Number` + `Maintenance Period` | `license_code`, `client_name`, `start_date`, `end_date`, `product_name`, `quantity` | | `esri_certificate_id_v1` | ESRI Indonesia | `Certificate ESRI Indonesia`, `Certificate Esri No`, `ESRI's License Certificate` | `esri_auth_number`, `client_name`, `product_name`, periode tahun di judul | | `esri_license_msg_v1` | Esri | File `.msg` subject `Esri License File for ArcGIS` | `order_number`, `product_name`, `client_name` (dari subject/body) | **Contoh path NAS (P1):** - `...\2026\PT Kalimantan Prima Persada #AzqehoVMExxV24ww\Sertifikat Software Global Mapper ... PO A17944.pdf` → `geonet_global_mapper_v1` - `...\2023\PT. Pertamina Hulu Kalimantan Timur\license 10.9.1\Certificate Esri No 583582 ....pdf` → `esri_certificate_id_v1` - `...\2023\PHE Holding\ESRI's License Certificate Pertamina Hulu Energi Holding 2021-2022.pdf` → `esri_certificate_id_v1` - `...\2023\PHE Holding\Esri License File for ArcGIS 10.x (...).msg` → `esri_license_msg_v1` #### Prioritas P2 — fase berikutnya | `template_id` | Vendor | Pola file / deteksi | Field utama | |---------------|--------|---------------------|-------------| | `geonet_global_mapper_msg_v1` | Blue Marble | `.msg` subject `Blue Marble Geographics License`, `License Instruction Global Mapper` | `license_code`, `order_number`, `client_name` | | `esri_sertifikat_legacy_v1` | ESRI | `Sertifikat License ESRI`, `Sertifikat ESRI -` | `client_name`, periode di nama file | | `mapinfo_certificate_v1` | MapInfo / Precisely | `Sertifikat MapInfo`, `Certificate MapInfo` | `client_name`, `product_name`, periode | | `geonet_license_docx_v1` | Geonet | `License.docx`, `License.pdf` di subfolder order | `license_code`, `client_name` | **Contoh path NAS (P2):** - `...\2023\PT. ADARO INDONESIA ...\Sertifikat GM_PT Adaro Indonesia.pdf` - `...\2023\PT. Ciptakridatama\...\License.pdf`, `License.docx` - `...\2023\PT. Minamas Plantation\Sertifikat MapInfo PT. Anugerah Sumber Makmur.pdf` - `...\2023\PT. GEOTRAV BHUANA SURVEY\Sertifikat PT. geotrav Bhuana Survey_05-08-2022.pdf` #### Prioritas P3 — butuh OCR atau parser khusus | `template_id` | Vendor | Pola file / deteksi | Catatan | |---------------|--------|---------------------|---------| | `license_image_ocr_v1` | Generic | `license.JPG`, `port license *.JPG`, `*.jpeg` screenshot aktivasi | Wajib OCR; confidence lebih rendah | | `safe_fme_v1` | Safe Software | FME license file | Jika muncul di share | | `geocalculator_v1` | Blue Marble | GeoCalculator license (sering di bundle GM) | Parse dari email/PDF terkait | | `concurrent_license_server_v1` | Various | `Concurrent License Server *.exe`, ZIP license | Metadata dari sidecar PDF/MSG | **Contoh path NAS (P3):** - `...\2023\Star Energy Wayang Windu\license.JPG` - `...\2023\PT. Pertamina Geothermal Energy Tbk\Globalmapper\port license 27000.JPG` - `...\2023\PT. Pamapersada Nusantara\*Server License*.jpeg` ### Prioritas indeks file license Indexer **tidak** parse semua 1,4 juta file sekaligus. Prioritas: ``` 1. **/project/*/*certificate* 2. **/project/*/*sertifikat* 3. **/project/*/*license* 4. **/project/*/*licence* 5. Folder dengan pola *#* di nama folder 6. Ekstensi: .pdf, .msg, .docx (P1–P2) 7. .jpg, .jpeg, .png → antrian OCR (P3) ``` Env indexer: ```env LICENSE_INDEX_PATTERNS=**/certificate*,**/sertifikat*,**/license*,**/*#* LICENSE_INDEX_EXTENSIONS=.pdf,.msg,.docx LICENSE_OCR_EXTENSIONS=.jpg,.jpeg,.png LICENSE_PARSER_VERSION=1.0.0 ``` ### API — license lookup | Endpoint | Method | Fungsi | |----------|--------|--------| | `/api/v1/license-search/ask` | POST | Natural language → lookup `license_registry` + Ollama format | | `/api/v1/license-search/code/{code}` | GET | Lookup exact `license_code` | | `/api/v1/license-search/expiring` | GET | Daftar license expired ≤ N hari | | `/api/v1/license-search/reindex` | POST | Trigger re-parse file (admin, audit) | **Request contoh:** ```json { "query": "license AzqehoVMExxV24ww milik siapa dan kapan expired?" } ``` **Response contoh (dari data terverifikasi):** ```json { "answer": "License AzqehoVMExxV24ww milik PT. Kalimantan Prima Persada. End date 09/06/2027 (maintenance 10/06/2026 – 09/06/2027). Produk: Global Mapper V27 Network License dan Global Mapper Pro add-on V27.", "matches": [ { "license_code": "AzqehoVMExxV24ww", "client_name": "PT. Kalimantan Prima Persada", "product_name": "Global Mapper V27 Network License", "quantity": 4, "start_date": "2026-06-10", "end_date": "2027-06-09", "order_number": "A17944", "template_id": "geonet_global_mapper_v1", "full_smb_path": "\\\\10.100.1.10\\project\\2026\\PT Kalimantan Prima Persada #AzqehoVMExxV24ww\\Sertifikat Software Global Mapper PT Kalimantan Prima Persada - PO A17944.pdf", "confidence_score": 98 } ], "source": "license_registry" } ``` ### Parser `geonet_global_mapper_v1` — field mapping Deteksi: teks mengandung `Certificate of Authenticity` **dan** `PT. Geonet Infomedia` **dan** `License Number`. | Label di PDF | Kolom DB | |--------------|----------| | End User Name | `client_name` | | Email | `client_email` | | Address | `client_address` | | License Number | `license_code` | | Maintenance Period Start / End Date | `start_date`, `end_date` | | Kolom Product | `product_name` | | Qty | `quantity` | | Nama file `PO A17944` | `order_number` (regex `PO [A-Z0-9]+`) | Format tanggal dokumen: `D/M/Y` (contoh `09/06/2027` → simpan ISO `2027-06-09`). ### Parser `esri_certificate_id_v1` — field mapping Deteksi: `Certificate ESRI Indonesia`, `Certificate Esri No`, atau `ESRI's License Certificate`. | Sumber | Kolom DB | |--------|----------| | `Certificate Esri No 583582` | `esri_auth_number` | | Nama klien di judul / body | `client_name` | | Periode di judul `2021-2022` | `start_date`, `end_date` (approx year range) | | Versi ArcGIS di path folder `10.9.1` | `product_version` | ### Kendala & mitigasi | Kendala | Mitigasi | |---------|----------| | PDF hasil scan (image) | OCR + flag `confidence_score` rendah | | Satu license code, banyak produk | Multi-row di `license_registry` | | Tanggal format bervariasi | Parser per template; simpan `date_format_hint` | | File `.msg` Outlook | Parser khusus; fallback ekstrak subject + attachment PDF | | LLM salah tanggal | **Wajib** jawab dari `license_registry`, bukan dari memory model | | Re-index setelah replace file | Bandingkan `file_sha256`; update baris jika hash berubah | ### Checklist License extraction - [ ] Tabel `license_vendors`, `license_document_templates`, `license_registry` di `.25` - [ ] Parser P1: `geonet_global_mapper_v1`, `esri_certificate_id_v1` - [ ] Index folder `project/2025`, `project/2026` (prioritas tahun terbaru) - [ ] Endpoint `/api/v1/license-search/code/{code}` - [ ] Alert `license_expiring_soon` (opsional → n8n / email) - [ ] Audit log query license (data sensitif) --- ## Spesifikasi host ### VM Ollama `10.100.1.26` (aiopr) — audit live 2026-06-15 | Resource | Nilai saat ini | Target implementasi | |----------|----------------|---------------------| | Hostname | `aiopr` | — | | IP | `10.100.1.26/24` (ens18) | — | | OS | Ubuntu **22.04.5 Desktop** | **Ubuntu Server 22.04** (disarankan) atau minimal tanpa GUI | | CPU | 8 vCPU Xeon E5-2699 v4, **AVX2** ✅ | **16 vCPU** (wajib naik) | | RAM | 62 GB (~60 GB free) ✅ | 64 GB — sudah memenuhi | | Disk `/` | 503 GB (468 GB free) ✅ | Sisakan **≥ 100 GB** untuk `OLLAMA_MODELS` | | GPU | Tidak ada (virtual VGA) | Tidak wajib fase CPU; GPU di Windows nanti | | Virtualisasi | KVM | — | | Jaringan → `.10` NAS | Ping OK | Wajib untuk indexer | | Jaringan → `.25` PostgreSQL | Port 5432 OK | Wajib | | Jaringan → `.24` geonet-console | Ping OK | Consumer `OLLAMA_BASE_URL` | | Ollama | Belum terinstall | Install + systemd | | Port `11434` | Belum listen | Bind internal VLAN saja | | Mount SMB | Belum ada | `cifs-utils` + mount read-only (indexer) | | SSH | Key-based (`root` / `geonet`) ✅ | `.\scripts\ssh-ollama.ps1` | **Verdict:** Host **cocok** untuk POC dan produksi CPU (Opsi B). Reverse proxy `10.100.1.24` **tidak** dipakai untuk Ollama — resource terbatas (15 GB RAM, Docker penuh). ### Kebutuhan tambahan di `10.100.1.26` (selain naik CPU ke 16) | # | Kategori | Item | Prioritas | Catatan | |---|----------|------|-----------|---------| | 1 | **Compute** | vCPU 8 → **16** | P0 | Target Opsi B; inference + indexer paralel | | 2 | **OS** | Ubuntu Desktop → **Server** atau matikan GUI/snap | P1 | Hemat ~2 GB RAM + CPU idle | | 3 | **Ollama** | Install Ollama + systemd enable | P0 | `curl -fsSL https://ollama.com/install.sh \| sh` | | 4 | **Model** | `ollama pull nomic-embed-text` | P0 | Embedding Fase 1 + 2 | | 5 | **Model** | `ollama pull qwen2.5:3b` (POC) lalu `qwen2.5:7b` (produksi CPU) | P0 | POC bisa mulai 3b, naik ke 7b setelah CPU 16 | | 6 | **Env Ollama** | `OLLAMA_HOST=0.0.0.0:11434`, `OLLAMA_MODELS=/data/ollama/models` | P0 | Lihat section [Env Ollama server](#env-ollama-server-bukan-folder-nas) | | 7 | **Storage model** | Folder `/data/ollama/models` (≥ 100 GB) | P0 | `qwen2.5:7b` + `nomic-embed-text` ≈ 8–10 GB | | 8 | **Firewall** | `ufw` aktif: allow `22`, allow `11434` dari `10.100.1.0/24` saja | P0 | Jangan expose ke internet | | 9 | **Integrasi app** | `OLLAMA_BASE_URL=http://10.100.1.26:11434` di `.env` geonet-console (`.24`) | P0 | Edit hanya di server | | 10 | **Indexer SMB** | `cifs-utils`, `smbclient`, mount `/mnt/qnap` read-only | P0 POC | Kredensial NAS hanya di server | | 11 | **Indexer worker** | Service crawl metadata → PostgreSQL `.25` | P0 POC | Bisa di VM ini untuk POC; **pisah** saat produksi penuh | | 12 | **PostgreSQL `.25`** | Extension **pgvector** + tabel `project_index` | P0 Fase 1 / P1 Fase 2 | Bukan di host `.26`, tapi wajib untuk stack | | 13 | **Monitoring** | Zabbix agent atau healthcheck `ollama ps` | P2 | Opsional POC | | 14 | **NTP** | `systemd-timesyncd` aktif | P1 | Audit log konsisten | **Tidak perlu di `.26`:** Docker (Ollama native cukup), GPU, mirror 2,6 TB NAS ke disk lokal, Nginx publik. ### Reverse proxy `10.100.1.24` — bukan host Ollama | Resource | Nilai | |----------|-------| | CPU | 8 vCPU (KVM, tanpa GPU) | | RAM | 15 GB total, ~12 GB available | | Disk `/` | 50 GB, ~32 GB free | | Docker RAM | ~1,5 GB (n8n, grafana, geonet-console, dll.) | | Peran | Gateway 39+ domain `*.gisportal.id` + geonet-console | **Verdict:** Tetap sebagai **reverse proxy & app host** saja. Jangan jalankan Ollama atau indexer 1,4 juta file di sini. ### Opsi A — POC minimum (tanpa GPU) — **fase awal di `.26`** | Resource | Spesifikasi | Status `.26` | |----------|-------------|---------------| | Host | `10.100.1.26` | ✅ | | vCPU | 8 (sementara) → **16** (target) | ⚠️ 8/16 | | RAM | 16–32 GB | ✅ 62 GB | | Disk | 100 GB SSD | ✅ 503 GB | | OS | Ubuntu 22.04 LTS | ⚠️ Desktop (perlu optimasi) | | Model | `qwen2.5:3b` + `nomic-embed-text` | ❌ belum pull | | User | 5–10 internal | — | ### Opsi B — Produksi Fase 2 RAG (CPU only) — **target host `.26`** | Resource | Spesifikasi | |----------|-------------| | Host | **`10.100.1.26`** | | vCPU | **16** (AVX2) | | RAM | **64 GB** | | Disk | **200 GB SSD** | | OS | Ubuntu 22.04 LTS | | Model | `qwen2.5:7b` + `nomic-embed-text` | | User | 10–30 internal | | Latensi estimasi | 5–15 detik/query (7B CPU) | ### Opsi C — Produksi optimal (GPU) — **target 6 bulan** | Resource | Spesifikasi | |----------|-------------| | Host | **PC Windows 11 Pro** (always-on) | | vCPU | 8–16 | | RAM | 32–64 GB | | GPU | NVIDIA **min 16 GB VRAM** (T4, L4, RTX 4060 Ti 16GB, A4000) | | Disk | 200 GB NVMe | | Model | `qwen2.5:14b` Q4 atau `llama3.1:8b` + `nomic-embed-text` | | Latensi estimasi | 2–8 detik/query | ### VM Indexer worker (terpisah dari Ollama) | Resource | Spesifikasi | |----------|-------------| | vCPU | 4–8 | | RAM | 8–16 GB | | Disk | 50 GB (hanya metadata, tidak mirror NAS) | | Network | Akses SMB ke `10.100.1.10`, API ke `.25` | | Job | Crawl incremental, ekstrak teks, kirim embedding | ### QNAP TS-932X — **jangan** jalankan Ollama produksi | Alasan | |--------| | ARM Cortex-A57, inferensi LLM lambat | | Storage pool 83–93% penuh | | Peran NAS = storage, bukan compute AI | --- ## Multi-folder via environment **Ollama tidak punya env untuk daftar folder SMB.** Konfigurasi di **indexer / geonet-console** (`.env` server). ### Contoh `.env` indexer ```env # Mount CIFS di Linux indexer NAS_MOUNT_BASE=/mnt/qnap NAS_SMB_HOST=10.100.1.10 NAS_SMB_USER=admin # NAS_SMB_PASS — hanya di server, jangan commit # Daftar path (Linux mount path) PROJECT_SEARCH_PATHS=/mnt/qnap/project,/mnt/qnap/Marketing/Nanda/DATA SALES,/mnt/qnap/GIS # Label untuk UI & filter RBAC PROJECT_SEARCH_SOURCES=project:Project NAS,marketing_sales:Marketing DATA SALES,gis:GIS # Exclude PROJECT_SEARCH_EXCLUDE=@Recycle,*.qcow2,*.iso,~$* # Jadwal crawl (cron) PROJECT_INDEX_CRON_PROJECT=0 2 * * * PROJECT_INDEX_CRON_MARKETING=0 3 * * 0 ``` ### Contoh `.env` aplikasi (geonet-console) ```env OLLAMA_BASE_URL=http://10.100.1.26:11434 OLLAMA_CHAT_MODEL=qwen2.5:7b OLLAMA_EMBED_MODEL=nomic-embed-text PROJECT_SEARCH_DEFAULT_SOURCES=project ``` Ganti IP saat migrasi Ubuntu → Windows GPU — **indeks PostgreSQL tidak perlu rebuild**. --- ## Model Ollama — daftar rekomendasi | Model | Fungsi | RAM/VRAM approx | Fase | |-------|--------|-----------------|------| | `nomic-embed-text` | Embedding semantic | ~0,3 GB | 1 + 2 | | `qwen2.5:3b` | Chat POC | ~4 GB | POC di `.26` | | `qwen2.5:7b` | Chat produksi CPU | ~8 GB | Fase 2 di `.26` (Opsi B) | | `qwen2.5:14b` | Chat produksi GPU | ~16 GB VRAM | Fase 2 (Opsi C) | Simpan daftar ini di repo; saat migrasi Windows gunakan `ollama pull` atau copy folder models. --- ## Skema database (PostgreSQL `.25`) ```sql -- Fase 1: metadata CREATE TABLE project_index ( id BIGSERIAL PRIMARY KEY, source_id VARCHAR(64) NOT NULL, -- 'project', 'marketing_sales', ... relative_path TEXT NOT NULL, full_smb_path TEXT NOT NULL, -- \\10.100.1.10\project\... client_name VARCHAR(255), year_folder SMALLINT, is_directory BOOLEAN NOT NULL DEFAULT true, file_ext VARCHAR(16), size_bytes BIGINT, mtime TIMESTAMPTZ, indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (source_id, relative_path) ); CREATE INDEX idx_project_index_client ON project_index USING gin (client_name gin_trgm_ops); CREATE INDEX idx_project_index_source_year ON project_index (source_id, year_folder); -- Fase 2: RAG chunks (butuh extension pgvector) CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE project_chunks ( id BIGSERIAL PRIMARY KEY, source_id VARCHAR(64) NOT NULL, file_path TEXT NOT NULL, chunk_index INT NOT NULL, chunk_text TEXT NOT NULL, embedding vector(768), indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` **License / sertifikat:** tabel `license_registry`, `license_vendors`, `license_document_templates` — lihat section [License / Certificate extraction](#license--certificate-extraction). --- ## API requirement (geonet-console) | Endpoint | Method | Fungsi | |----------|--------|--------| | `/api/v1/project-search/ask` | POST | Query natural language + optional `sources[]` | | `/api/v1/project-search/index/status` | GET | Status indeks per source | | `/api/v1/project-search/sources` | GET | Daftar source dari env | | `/api/v1/license-search/ask` | POST | Query license natural language (lihat section License) | | `/api/v1/license-search/code/{code}` | GET | Lookup exact license code | | `/api/v1/license-search/expiring` | GET | License mendekati expired | **Request contoh:** ```json { "query": "carikan projek Pertamina", "sources": ["project"], "limit": 20 } ``` **Response contoh:** ```json { "answer": "Ditemukan 9 folder proyek Pertamina di \\\\10.100.1.10\\project ...", "results": [ { "source_id": "project", "path": "\\\\10.100.1.10\\project\\2025\\PT. Pertamina Geothermal Energy Tbk", "year_folder": 2025, "mtime": "2025-03-05T09:56:00+07:00" } ] } ``` **Wajib enterprise:** - Auth LDAP / API token - Audit log: user, query, sources, IP, duration - Throttle API - RBAC per source (mis. `marketing_sales` hanya role Sales) --- ## Migrasi Ollama: Ubuntu 22 → Windows 11 + GPU ### Yang portable | Item | Portable | |------|----------| | File model (`~/.ollama/models/`) | ✅ | | Custom Modelfile | ✅ | | Indeks pgvector di `.25` | ✅ tetap, tidak pindah | | Metadata `project_index` | ✅ tetap | ### Langkah migrasi 1. Catat model: `ollama list` → simpan ke repo 2. Deploy Ollama Windows + driver NVIDIA 3. **Opsi A:** copy `models/` dari Ubuntu **Opsi B (disarankan):** `ollama pull qwen2.5:7b` + `nomic-embed-text` 4. Update `OLLAMA_BASE_URL` di `.env` geonet-console 5. Update Nginx internal jika perlu 6. Verifikasi GPU: Task Manager / log Ollama saat inference ### Path model | OS | Path | |----|------| | Ubuntu | `~/.ollama/models/` | | Windows | `C:\Users\\.ollama\models\` | --- ## Env Ollama server (bukan folder NAS) ```env OLLAMA_HOST=0.0.0.0:11434 OLLAMA_MODELS=/data/ollama/models OLLAMA_NUM_PARALLEL=2 OLLAMA_MAX_LOADED_MODELS=2 ``` Bind `11434` hanya jaringan internal (`10.100.1.0/24`). Jangan expose ke internet. --- ## Keamanan | Item | Requirement | |------|-------------| | Kredensial SMB NAS | Hanya di `.env` server indexer | | Ollama API | Internal VLAN saja | | Data sales / project | RBAC per `source_id` | | Audit | Log setiap query + hasil count | | Password NAS | Rotasi jika pernah dibagikan di chat | --- ## Timeline rekomendasi | Tahap | Durasi | Deliverable | |-------|--------|-------------| | POC metadata | 1–2 minggu | Index `project` saja, chat di console | | Multi-source | +1 minggu | Tambah Marketing/GIS via env | | Fase 2 RAG | 2–4 minggu | pgvector + ekstrak dokumen | | License extraction P1 | +1–2 minggu | `license_registry` + parser GM & ESRI | | Migrasi GPU | ~6 bulan | Windows 11 + GPU, ganti `OLLAMA_BASE_URL` | --- ## Host referensi (repo) | Host | IP | Peran | |------|-----|-------| | QNAP NAS | `10.100.1.10` | SMB storage | | **Ollama VM** | **`10.100.1.26`** | **Ollama inference + indexer POC** (`aiopr`) | | Reverse proxy | `10.100.1.24` | Nginx, Docker, geonet-console | | Main database | `10.100.1.25` | PostgreSQL + pgvector | | geonet-console | via `.24` | UI + API — consumer `OLLAMA_BASE_URL` | SSH Ollama: `.\scripts\ssh-ollama.ps1` atau `ssh ollama` SSH NAS: `.\scripts\ssh-qnap.ps1` SSH reverse proxy: `.\scripts\ssh-remote.ps1` --- ## Checklist sebelum go-live ### Host `10.100.1.26` - [ ] vCPU dinaikkan ke **16** - [ ] OS dioptimasi (Server 22.04 atau tanpa GUI) - [ ] Ollama terinstall + systemd aktif - [ ] Model `nomic-embed-text` + `qwen2.5:3b`/`7b` ter-pull - [ ] `OLLAMA_HOST` bind internal; `ufw` allow `11434` dari `10.100.1.0/24` - [ ] Folder `/data/ollama/models` siap - [ ] Mount SMB QNAP read-only (`/mnt/qnap`) untuk indexer POC - [ ] Indexer worker jalan (metadata `project` → PostgreSQL) ### Stack keseluruhan - [ ] PostgreSQL pgvector + tabel `project_index` di `.25` - [ ] `OLLAMA_BASE_URL=http://10.100.1.26:11434` di geonet-console (`.env` server `.24`) - [ ] Audit log pencarian aktif - [ ] RBAC source sensitif (Marketing/Sales) - [ ] Rencana migrasi Windows GPU terdokumentasi - [ ] Tabel `license_registry` + parser P1 (`geonet_global_mapper_v1`, `esri_certificate_id_v1`) - [ ] Index license folder `project/2025`–`2026` terlebih dahulu --- ## Referensi terkait - `server/10.100.1.10_audit.txt` — audit QNAP - `server/reverse-proxy-audit.txt` — audit reverse proxy - `server/main-database-audit.txt` — PostgreSQL `.25` - `server/host.txt` — daftar SSH host