Browse Source
Refactor install/update into Agent-InstallCore, add GPO startup scripts, GPU and disk model collection, DB migration 002, and collector UI updates. Co-authored-by: Cursor <cursoragent@cursor.com>master
30 changed files with 1452 additions and 333 deletions
@ -1,120 +1,239 @@ |
|||||||
# GeoNetAgent — Progress & Monitoring Fase
|
# GeoNetAgent — Progress & Monitoring Fase |
||||||
|
|
||||||
> **Update file ini** setiap milestone selesai. Ini sumber tunggal untuk cek *kita ada di fase mana*.
|
|
||||||
|
|
||||||
**Terakhir diupdate:** 2026-06-17 (web install agent · domain gisportal.id · reverse proxy)
|
> **Update file ini** setiap milestone selesai. Ini sumber tunggal untuk cek *kita ada di fase mana*. |
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Snapshot (baca cepat)
|
**Terakhir diupdate:** 2026-06-17 (web install agent · domain gisportal.id · reverse proxy) |
||||||
|
|
||||||
| Item | Status |
|
|
||||||
|------|--------|
|
|
||||||
| **Fase aktif** | **Fase 1 — Foundation** 🔄 |
|
--- |
||||||
| Fase berikutnya | Laravel asset index · auth IT |
|
|
||||||
| Agent POC | `v0.1.6-phase0` — collect + POST + web install |
|
|
||||||
| Collector | `https://collector.gisportal.id` UI · `agent.gisportal.id` POST · `:8000` HTTP LAN |
|
|
||||||
| Monorepo | Partial — `agent/` + `collector/` + `samples/` + `infra/` ✅ · `portal/` ❌ |
|
## Snapshot (baca cepat) |
||||||
| Blocker utama | Laravel portal belum scaffold |
|
|
||||||
|
|
||||||
### Progress ringkas
|
|
||||||
|
| Item | Status | |
||||||
| Fase | Status | Progress |
|
|
||||||
|------|--------|----------|
|
|------|--------| |
||||||
| 0 — Blueprint | ✅ Selesai | 8 / 8 |
|
|
||||||
| 1 — Foundation | 🔄 Aktif | 7 / 9 |
|
| **Fase aktif** | **Fase 1 — Foundation** 🔄 | |
||||||
| 2 — Integrasi monitoring | ⏳ | 0 / 2 |
|
|
||||||
| 3 — Infra sync & CMDB | ⏳ | 0 / 3 |
|
| Fase berikutnya | Laravel asset index · auth IT | |
||||||
| 4 — Scale & intelligence | ⏳ | 1 / 3 |
|
|
||||||
|
| Agent POC | `v0.1.6-phase0` — collect + POST + web install | |
||||||
---
|
|
||||||
|
| Collector | `https://collector.gisportal.id` UI · `agent.gisportal.id` POST · `:8000` HTTP LAN | |
||||||
## Fase 0 — Blueprint ✅ **(SELESAI)**
|
|
||||||
|
| Monorepo | Partial — `agent/` + `collector/` + `samples/` + `infra/` ✅ · `portal/` ❌ | |
||||||
**DoD:** Dokumentasi lengkap, blueprint disetujui user, struktur monorepo ter-scaffold.
|
|
||||||
|
| Blocker utama | Laravel portal belum scaffold | |
||||||
- [x] Dokumentasi `.cursor/` lengkap (blueprint, db, API, repo, monitoring, playbook)
|
|
||||||
- [x] `server.md` + SSH key-only diverifikasi
|
|
||||||
- [x] Rule `project-context.mdc` (server, Dockge, QNAP Object Storage)
|
|
||||||
- [x] Scaffold partial — folder `agent/` + `samples/agent-reports/`
|
### Progress ringkas |
||||||
- [x] Uji agent collect-only di laptop dev (`DESKTOP-RRJ9G01`)
|
|
||||||
- [ ] Review & approval user terhadap blueprint
|
|
||||||
- [ ] Scaffold monorepo penuh (`portal/`)
|
|
||||||
- [x] Pindahkan sample DXDiag ke `samples/dxdiag/`
|
| Fase | Status | Progress | |
||||||
|
|
||||||
### Sub-task: Agent POC
|
|------|--------|----------| |
||||||
|
|
||||||
- [x] `Collect-GeoNetReport.ps1` + modul `lib/`
|
| 0 — Blueprint | ✅ Selesai | 8 / 8 | |
||||||
- [x] Payload: system, disk, memory, network, health, peripherals, monitor
|
|
||||||
- [x] `Send-GeoNetReport.ps1` — E2E POST ke collector
|
| 1 — Foundation | 🔄 Aktif | 7 / 9 | |
||||||
- [x] Web install `https://agent.gisportal.id/install/` — pilot `DESKTOP-RRJ9G01`
|
|
||||||
- [ ] Uji collect di laptop kedua (`BKK-MINIPC`)
|
| 2 — Integrasi monitoring | ⏳ | 0 / 2 | |
||||||
|
|
||||||
---
|
| 3 — Infra sync & CMDB | ⏳ | 0 / 3 | |
||||||
|
|
||||||
## Fase 1 — Foundation 🔄 **(AKTIF)**
|
| 4 — Scale & intelligence | ⏳ | 1 / 3 | |
||||||
|
|
||||||
**DoD:** Agent di 1 laptop test POST → data di DB → Laravel tampilkan halaman asset list.
|
|
||||||
|
|
||||||
- [x] PostgreSQL `geonetagent_dev` + SQL migration `001_initial_schema` di main-db
|
--- |
||||||
- [x] FastAPI `/api/v1/agent/report` + `/health` + read API assets
|
|
||||||
- [x] PowerShell agent v0.1.6 + kirim ke collector (E2E verified)
|
|
||||||
- [x] UI uji read-only — `https://collector.gisportal.id/ui/` · `http://10.100.1.24:8000/ui/`
|
|
||||||
- [x] Nginx reverse proxy — POST agent `agent.gisportal.id` · HTTPS UI `collector.gisportal.id`
|
## Fase 0 — Blueprint ✅ **(SELESAI)** |
||||||
- [x] DNS publik + split-DNS LAN `agent.gisportal.id` / `collector.gisportal.id`
|
|
||||||
- [x] Firewalld port `8000` untuk subnet internal di `.24`
|
|
||||||
- [ ] Laravel asset index (read-only)
|
|
||||||
- [ ] Auth IT user lokal (M9)
|
**DoD:** Dokumentasi lengkap, blueprint disetujui user, struktur monorepo ter-scaffold. |
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fase 2 — Integrasi monitoring ⏳
|
- [x] Dokumentasi `.cursor/` lengkap (blueprint, db, API, repo, monitoring, playbook) |
||||||
|
|
||||||
**DoD:** Alert Zabbix di portal; status host dari Zabbix API.
|
- [x] `server.md` + SSH key-only diverifikasi |
||||||
|
|
||||||
- [ ] Zabbix webhook → Laravel
|
- [x] Rule `project-context.mdc` (server, Dockge, QNAP Object Storage) |
||||||
- [ ] Zabbix host status cache di dashboard
|
|
||||||
|
- [x] Scaffold partial — folder `agent/` + `samples/agent-reports/` |
||||||
---
|
|
||||||
|
- [x] Uji agent collect-only di laptop dev (`DESKTOP-RRJ9G01`) |
||||||
## Fase 3 — Infra sync & CMDB ⏳
|
|
||||||
|
- [ ] Review & approval user terhadap blueprint |
||||||
**DoD:** VM Proxmox otomatis di CMDB; license manual bisa di-assign.
|
|
||||||
|
- [ ] Scaffold monorepo penuh (`portal/`) |
||||||
- [ ] Proxmox sync job (FastAPI)
|
|
||||||
- [ ] License module (Laravel)
|
- [x] Pindahkan sample DXDiag ke `samples/dxdiag/` |
||||||
- [ ] User ↔ device assignment UI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
### Sub-task: Agent POC |
||||||
## Fase 4 — Scale & intelligence ⏳
|
|
||||||
|
|
||||||
- [ ] GPO deploy agent ke semua laptop (web install siap; GPO opsional)
|
|
||||||
- [x] Web install `https://agent.gisportal.id/install/` + `Install-GeoNetAgent.ps1`
|
- [x] `Collect-GeoNetReport.ps1` + modul `lib/` |
||||||
- [ ] Trend & compliance report
|
|
||||||
- [ ] Ollama assistant (opsional)
|
- [x] Payload: system, disk, memory, network, health, peripherals, monitor |
||||||
|
|
||||||
---
|
- [x] `Send-GeoNetReport.ps1` — E2E POST ke collector |
||||||
|
|
||||||
## Cara update
|
- [x] Web install `https://agent.gisportal.id/install/` — pilot `DESKTOP-RRJ9G01` |
||||||
|
|
||||||
1. Centang `[x]` item yang selesai
|
- [ ] Uji collect di laptop kedua (`BKK-MINIPC`) |
||||||
2. Update **Terakhir diupdate** dan tabel **Snapshot**
|
|
||||||
3. Hitung ulang kolom **Progress** (item selesai / total item fase)
|
|
||||||
4. Jika pindah fase: ubah **Fase aktif** di Snapshot + banner `(AKTIF)` pada section fase
|
|
||||||
5. Sinkronkan baris status singkat di `blueprint.md`, `me.md`, `monitoring-architecture.md`, `project-context.mdc`, `perintah.md`
|
--- |
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Riwayat milestone
|
## Fase 1 — Foundation 🔄 **(AKTIF)** |
||||||
|
|
||||||
| Tanggal | Milestone |
|
|
||||||
|---------|-----------|
|
|
||||||
| 2026-06-17 | Dokumentasi `.cursor/` + SSH server verified |
|
**DoD:** Agent di 1 laptop test POST → data di DB → Laravel tampilkan halaman asset list. |
||||||
| 2026-06-17 | Agent POC `Collect-GeoNetReport.ps1` v0.1.2 — collect JSON lokal |
|
|
||||||
| 2026-06-17 | File `progress.md` dibuat — tracking fase terpusat |
|
|
||||||
| 2026-06-17 | Agent v0.1.4 — `peripherals[]` USB/HID/BT/audio jack |
|
|
||||||
| 2026-06-17 | Collector redeploy `.24` + UI peripherals di `/ui/` |
|
- [x] PostgreSQL `geonetagent_dev` + SQL migration `001_initial_schema` di main-db |
||||||
| 2026-06-17 | Nginx reverse proxy — domain `gisportal.id` |
|
|
||||||
| 2026-06-17 | Web install agent + pilot deploy `DESKTOP-RRJ9G01` |
|
- [x] FastAPI `/api/v1/agent/report` + `/health` + read API assets |
||||||
|
|
||||||
|
- [x] PowerShell agent v0.1.6 + kirim ke collector (E2E verified) |
||||||
|
|
||||||
|
- [x] UI uji read-only — `https://collector.gisportal.id/ui/` · `http://10.100.1.24:8000/ui/` |
||||||
|
|
||||||
|
- [x] Nginx reverse proxy — POST agent `agent.gisportal.id` · HTTPS UI `collector.gisportal.id` |
||||||
|
|
||||||
|
- [x] DNS publik + split-DNS LAN `agent.gisportal.id` / `collector.gisportal.id` |
||||||
|
|
||||||
|
- [x] Firewalld port `8000` untuk subnet internal di `.24` |
||||||
|
|
||||||
|
- [ ] Laravel asset index (read-only) |
||||||
|
|
||||||
|
- [ ] Auth IT user lokal (M9) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Fase 2 — Integrasi monitoring ⏳ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**DoD:** Alert Zabbix di portal; status host dari Zabbix API. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] Zabbix webhook → Laravel |
||||||
|
|
||||||
|
- [ ] Zabbix host status cache di dashboard |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Fase 3 — Infra sync & CMDB ⏳ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**DoD:** VM Proxmox otomatis di CMDB; license manual bisa di-assign. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] Proxmox sync job (FastAPI) |
||||||
|
|
||||||
|
- [ ] License module (Laravel) |
||||||
|
|
||||||
|
- [ ] User ↔ device assignment UI |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Fase 4 — Scale & intelligence ⏳ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] GPO deploy agent ke semua laptop (web install siap; GPO opsional) |
||||||
|
|
||||||
|
- [x] Web install `https://agent.gisportal.id/install/` + `Install-GeoNetAgent.ps1` |
||||||
|
|
||||||
|
- [ ] Trend & compliance report |
||||||
|
|
||||||
|
- [ ] Ollama assistant (opsional) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Cara update |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. Centang `[x]` item yang selesai |
||||||
|
|
||||||
|
2. Update **Terakhir diupdate** dan tabel **Snapshot** |
||||||
|
|
||||||
|
3. Hitung ulang kolom **Progress** (item selesai / total item fase) |
||||||
|
|
||||||
|
4. Jika pindah fase: ubah **Fase aktif** di Snapshot + banner `(AKTIF)` pada section fase |
||||||
|
|
||||||
|
5. Sinkronkan baris status singkat di `blueprint.md`, `me.md`, `monitoring-architecture.md`, `project-context.mdc`, `perintah.md` |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Riwayat milestone |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Tanggal | Milestone | |
||||||
|
|
||||||
|
|---------|-----------| |
||||||
|
|
||||||
|
| 2026-06-17 | Dokumentasi `.cursor/` + SSH server verified | |
||||||
|
|
||||||
|
| 2026-06-17 | Agent POC `Collect-GeoNetReport.ps1` v0.1.2 — collect JSON lokal | |
||||||
|
|
||||||
|
| 2026-06-17 | File `progress.md` dibuat — tracking fase terpusat | |
||||||
|
|
||||||
|
| 2026-06-17 | Agent v0.1.4 — `peripherals[]` USB/HID/BT/audio jack | |
||||||
|
|
||||||
|
| 2026-06-17 | Collector redeploy `.24` + UI peripherals di `/ui/` | |
||||||
|
|
||||||
|
| 2026-06-17 | Nginx reverse proxy — domain `gisportal.id` | |
||||||
|
|
||||||
|
| 2026-06-17 | Web install agent + pilot deploy `DESKTOP-RRJ9G01` | |
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,6 @@ |
|||||||
|
@echo off |
||||||
|
REM GeoNetAgent — GPO Computer Startup Script (wrapper) |
||||||
|
REM Deploy folder ini ke SYSVOL / NETLOGON, lalu tautkan .cmd ini di GPO Startup. |
||||||
|
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0GeoNetAgent-GPO-Startup.ps1" |
||||||
|
exit /b 0 |
||||||
@ -0,0 +1,185 @@ |
|||||||
|
#Requires -Version 5.1 |
||||||
|
<# |
||||||
|
.SYNOPSIS |
||||||
|
GPO Startup Script — pasang atau update GeoNetAgent (silent, SYSTEM). |
||||||
|
|
||||||
|
.DESCRIPTION |
||||||
|
Dipanggil dari Group Policy (Computer Configuration → Startup Scripts). |
||||||
|
- Belum terpasang → install dari https://agent.gisportal.id/install/ |
||||||
|
- Sudah terpasang → cek manifest vs VERSION, update jika perlu |
||||||
|
- Memastikan Scheduled Task ada (startup + tiap 3 jam) |
||||||
|
- Tidak kirim laporan langsung (-SkipInitialRun); task SYSTEM yang kirim |
||||||
|
|
||||||
|
.PARAMETER BaseUrl |
||||||
|
URL paket install. Default: https://agent.gisportal.id/install |
||||||
|
|
||||||
|
.PARAMETER ScheduleHours |
||||||
|
Interval laporan terjadwal. Default: 3 |
||||||
|
|
||||||
|
.PARAMETER MaxNetworkWaitSeconds |
||||||
|
Tunggu jaringan siap sebelum unduh (boot awal). Default: 120 |
||||||
|
|
||||||
|
.NOTES |
||||||
|
Log: %ProgramData%\GeoNetAgent\logs\gpo-startup.log |
||||||
|
Exit selalu 0 agar GPO tidak mengulang agresif; cek log jika perlu. |
||||||
|
#> |
||||||
|
[CmdletBinding()] |
||||||
|
param( |
||||||
|
[string]$BaseUrl = 'https://agent.gisportal.id/install', |
||||||
|
[int]$ScheduleHours = 3, |
||||||
|
[int]$MaxNetworkWaitSeconds = 120 |
||||||
|
) |
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue' |
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||||
|
|
||||||
|
$InstallRoot = Join-Path $env:ProgramData 'GeoNetAgent' |
||||||
|
$LogDir = Join-Path $InstallRoot 'logs' |
||||||
|
$LogFile = Join-Path $LogDir 'gpo-startup.log' |
||||||
|
|
||||||
|
function Write-GeoNetGpoLog { |
||||||
|
param([string]$Message) |
||||||
|
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message |
||||||
|
try { |
||||||
|
if (-not (Test-Path $LogDir)) { |
||||||
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null |
||||||
|
} |
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8 |
||||||
|
} |
||||||
|
catch { } |
||||||
|
} |
||||||
|
|
||||||
|
function Wait-GeoNetNetwork { |
||||||
|
param( |
||||||
|
[string]$Url, |
||||||
|
[int]$MaxSeconds |
||||||
|
) |
||||||
|
$base = $Url.TrimEnd('/') |
||||||
|
$probe = "$base/manifest.json" |
||||||
|
$deadline = (Get-Date).AddSeconds($MaxSeconds) |
||||||
|
while ((Get-Date) -lt $deadline) { |
||||||
|
try { |
||||||
|
$null = Invoke-WebRequest -Uri $probe -Method Head -UseBasicParsing -TimeoutSec 8 |
||||||
|
return $true |
||||||
|
} |
||||||
|
catch { |
||||||
|
Start-Sleep -Seconds 5 |
||||||
|
} |
||||||
|
} |
||||||
|
return $false |
||||||
|
} |
||||||
|
|
||||||
|
function Save-GeoNetRemoteFile { |
||||||
|
param([string]$Url, [string]$Destination) |
||||||
|
$parent = Split-Path -Parent $Destination |
||||||
|
if (-not (Test-Path $parent)) { |
||||||
|
New-Item -ItemType Directory -Path $parent -Force | Out-Null |
||||||
|
} |
||||||
|
try { |
||||||
|
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -TimeoutSec 90 |
||||||
|
if ((Get-Item $Destination).Length -gt 0) { return } |
||||||
|
} |
||||||
|
catch { |
||||||
|
if (Get-Command curl.exe -ErrorAction SilentlyContinue) { |
||||||
|
& curl.exe -fsSL --tlsv1.2 $Url -o $Destination |
||||||
|
if ($LASTEXITCODE -eq 0 -and (Test-Path $Destination)) { return } |
||||||
|
} |
||||||
|
throw |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Invoke-GeoNetGpoWebInstall { |
||||||
|
param([string]$Url) |
||||||
|
$base = $Url.TrimEnd('/') |
||||||
|
$stage = Join-Path $env:TEMP 'GeoNetAgent-gpo-install' |
||||||
|
$installer = Join-Path $stage 'Install-GeoNetAgent.ps1' |
||||||
|
$coreLib = Join-Path $stage 'lib\Agent-InstallCore.ps1' |
||||||
|
|
||||||
|
Write-GeoNetGpoLog "Unduh installer dari $base" |
||||||
|
Save-GeoNetRemoteFile -Url "$base/Install-GeoNetAgent.ps1" -Destination $installer |
||||||
|
Save-GeoNetRemoteFile -Url "$base/lib/Agent-InstallCore.ps1" -Destination $coreLib |
||||||
|
|
||||||
|
Write-GeoNetGpoLog 'Jalankan Install-GeoNetAgent.ps1 -SkipInitialRun' |
||||||
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer ` |
||||||
|
-BaseUrl $base -InstallRoot $InstallRoot -SkipInitialRun -ScheduleHours $ScheduleHours |
||||||
|
if ($LASTEXITCODE -ne 0) { |
||||||
|
throw "Install-GeoNetAgent exit code $LASTEXITCODE" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Test-GeoNetScheduledTasks { |
||||||
|
$names = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') |
||||||
|
foreach ($name in $names) { |
||||||
|
$null = & schtasks.exe /Query /TN $name /FO LIST 2>$null |
||||||
|
if ($LASTEXITCODE -ne 0) { return $false } |
||||||
|
} |
||||||
|
return $true |
||||||
|
} |
||||||
|
|
||||||
|
function Repair-GeoNetScheduledTasks { |
||||||
|
param([string]$Root, [int]$Hours) |
||||||
|
$core = Join-Path $Root 'lib\Agent-InstallCore.ps1' |
||||||
|
if (-not (Test-Path $core)) { |
||||||
|
throw "Agent-InstallCore.ps1 tidak ada di $Root" |
||||||
|
} |
||||||
|
. $core |
||||||
|
Register-GeoNetAgentTask -InstallRoot $Root -Hours $Hours |
||||||
|
Write-GeoNetGpoLog "Scheduled Task diperbaiki (interval ${Hours} jam)" |
||||||
|
} |
||||||
|
|
||||||
|
Write-GeoNetGpoLog '=== GPO startup begin ===' |
||||||
|
|
||||||
|
try { |
||||||
|
if (-not (Wait-GeoNetNetwork -Url $BaseUrl -MaxSeconds $MaxNetworkWaitSeconds)) { |
||||||
|
throw "Jaringan/install URL tidak reachable dalam ${MaxNetworkWaitSeconds}s" |
||||||
|
} |
||||||
|
|
||||||
|
$userAgent = Join-Path $InstallRoot 'UserAgent.ps1' |
||||||
|
$updateScript = Join-Path $InstallRoot 'lib\Update-GeoNetAgent.ps1' |
||||||
|
|
||||||
|
if (-not (Test-Path $userAgent)) { |
||||||
|
Write-GeoNetGpoLog 'Agent belum terpasang — install baru' |
||||||
|
Invoke-GeoNetGpoWebInstall -Url $BaseUrl |
||||||
|
} |
||||||
|
elseif (-not (Test-Path $updateScript)) { |
||||||
|
Write-GeoNetGpoLog 'Agent lama (tanpa auto-update) — reinstall' |
||||||
|
Invoke-GeoNetGpoWebInstall -Url $BaseUrl |
||||||
|
} |
||||||
|
else { |
||||||
|
Write-GeoNetGpoLog 'Agent ada — cek update' |
||||||
|
. $updateScript |
||||||
|
$result = Update-GeoNetAgentIfNeeded ` |
||||||
|
-InstallRoot $InstallRoot ` |
||||||
|
-BaseUrl $BaseUrl ` |
||||||
|
-ConfigPath (Join-Path $InstallRoot 'config.json') ` |
||||||
|
-ScheduleHours $ScheduleHours ` |
||||||
|
-Quiet |
||||||
|
if ($result.Updated) { |
||||||
|
Write-GeoNetGpoLog "Updated: $($result.From) -> $($result.To)" |
||||||
|
} |
||||||
|
else { |
||||||
|
Write-GeoNetGpoLog "Versi OK: $($result.From)" |
||||||
|
if ($result.Error) { |
||||||
|
Write-GeoNetGpoLog "Update check warning: $($result.Error)" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (-not (Test-GeoNetScheduledTasks)) { |
||||||
|
Write-GeoNetGpoLog 'Scheduled Task hilang — perbaiki' |
||||||
|
Repair-GeoNetScheduledTasks -Root $InstallRoot -Hours $ScheduleHours |
||||||
|
} |
||||||
|
|
||||||
|
$version = $null |
||||||
|
$versionPath = Join-Path $InstallRoot 'VERSION' |
||||||
|
if (Test-Path $versionPath) { |
||||||
|
$version = (Get-Content $versionPath -Raw).Trim() |
||||||
|
} |
||||||
|
Write-GeoNetGpoLog "Selesai OK. VERSION=$version" |
||||||
|
} |
||||||
|
catch { |
||||||
|
Write-GeoNetGpoLog "ERROR: $($_.Exception.Message)" |
||||||
|
} |
||||||
|
|
||||||
|
Write-GeoNetGpoLog '=== GPO startup end ===' |
||||||
|
exit 0 |
||||||
@ -0,0 +1,146 @@ |
|||||||
|
# GeoNetAgent — Rollout massal via GPO |
||||||
|
|
||||||
|
Skrip startup untuk **Computer Configuration** Active Directory. Jalan sebagai **SYSTEM** saat boot — tidak perlu user Admin login. |
||||||
|
|
||||||
|
## Isi folder |
||||||
|
|
||||||
|
| File | Fungsi | |
||||||
|
|------|--------| |
||||||
|
| `GeoNetAgent-GPO-Startup.cmd` | Entry point untuk GPO (tambahkan file ini di policy) | |
||||||
|
| `GeoNetAgent-GPO-Startup.ps1` | Logika install / update / perbaiki task | |
||||||
|
| `README.md` | Panduan ini | |
||||||
|
|
||||||
|
## Prasyarat server |
||||||
|
|
||||||
|
1. Agent v0.1.8+ sudah di-publish: `bash infra/collector/publish_agent_install.sh` |
||||||
|
2. `https://agent.gisportal.id/install/manifest.json` bisa diakses dari jaringan user |
||||||
|
3. Migration DB `002_disk_model.sql` sudah dijalankan (jika pakai fitur model disk) |
||||||
|
|
||||||
|
## Langkah 1 — Salin ke SYSVOL |
||||||
|
|
||||||
|
Salin seluruh folder `agent/gpo/` ke share domain, contoh: |
||||||
|
|
||||||
|
``` |
||||||
|
\\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\ |
||||||
|
``` |
||||||
|
|
||||||
|
Atau (klassik): |
||||||
|
|
||||||
|
``` |
||||||
|
\\<DC>\NETLOGON\GeoNetAgent\ |
||||||
|
``` |
||||||
|
|
||||||
|
Pastikan **Computer Account** domain bisa **Read** folder tersebut. |
||||||
|
|
||||||
|
## Langkah 2 — Buat / edit GPO |
||||||
|
|
||||||
|
1. Buka **Group Policy Management** di DC (`AD 2012 R2` OK) |
||||||
|
2. Buat GPO baru, mis. `GeoNetAgent — Deploy Endpoint` |
||||||
|
3. Edit GPO → **Computer Configuration** → **Policies** → **Windows Settings** → **Scripts (Startup/Shutdown)** |
||||||
|
4. Tab **Startup** → **Add** → pilih: |
||||||
|
|
||||||
|
``` |
||||||
|
\\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\GeoNetAgent-GPO-Startup.cmd |
||||||
|
``` |
||||||
|
|
||||||
|
5. **OK** → tutup editor GPO |
||||||
|
|
||||||
|
## Langkah 3 — Link ke OU |
||||||
|
|
||||||
|
- Link GPO ke OU yang berisi **laptop/PC Windows** user |
||||||
|
- **Jangan** link ke OU server jika server tidak perlu agent |
||||||
|
- Urutan: taruh GPO di level OU yang tepat; hindari konflik dengan GPO lain yang memblokir script |
||||||
|
|
||||||
|
## Langkah 4 — Uji (1–2 mesin dulu) |
||||||
|
|
||||||
|
Pada laptop uji (sudah join domain): |
||||||
|
|
||||||
|
```cmd |
||||||
|
gpupdate /force |
||||||
|
shutdown /r /t 0 |
||||||
|
``` |
||||||
|
|
||||||
|
Setelah boot, cek: |
||||||
|
|
||||||
|
```powershell |
||||||
|
Get-Content "$env:ProgramData\GeoNetAgent\VERSION" |
||||||
|
Get-Content "$env:ProgramData\GeoNetAgent\logs\gpo-startup.log" -Tail 20 |
||||||
|
schtasks /Query /TN GeoNetAgent-Report /V /FO LIST |
||||||
|
Test-Path "$env:ProgramData\GeoNetAgent\UserAgent.ps1" |
||||||
|
``` |
||||||
|
|
||||||
|
Harapan: |
||||||
|
|
||||||
|
- `VERSION` = versi terbaru di server (mis. `0.1.8-phase0`) |
||||||
|
- Log berisi `Selesai OK` |
||||||
|
- Task `GeoNetAgent-Report` — HOURLY, interval **3** jam, run as **SYSTEM** |
||||||
|
|
||||||
|
## Perilaku skrip |
||||||
|
|
||||||
|
| Kondisi | Aksi | |
||||||
|
|---------|------| |
||||||
|
| Agent belum ada | Install penuh dari web (silent, `-SkipInitialRun`) | |
||||||
|
| Agent lama tanpa modul auto-update | Reinstall | |
||||||
|
| Agent ada | Cek `manifest.json` vs `VERSION`, update jika beda | |
||||||
|
| Scheduled Task hilang | Daftar ulang (startup + 3 jam) | |
||||||
|
| Jaringan belum siap saat boot | Tunggu hingga 120 detik | |
||||||
|
| Error | Catat di log, **exit 0** (boot tidak terganggu) | |
||||||
|
|
||||||
|
Laporan inventaris dikirim oleh Scheduled Task (`UserAgent.ps1`), bukan langsung dari skrip GPO. |
||||||
|
|
||||||
|
## Log |
||||||
|
|
||||||
|
``` |
||||||
|
%ProgramData%\GeoNetAgent\logs\gpo-startup.log |
||||||
|
``` |
||||||
|
|
||||||
|
Contoh: |
||||||
|
|
||||||
|
``` |
||||||
|
2026-06-18 08:15:02 === GPO startup begin === |
||||||
|
2026-06-18 08:15:08 Agent belum terpasang — install baru |
||||||
|
2026-06-18 08:15:45 Selesai OK. VERSION=0.1.8-phase0 |
||||||
|
2026-06-18 08:15:45 === GPO startup end === |
||||||
|
``` |
||||||
|
|
||||||
|
## Firewall / proxy |
||||||
|
|
||||||
|
Laptop harus bisa **HTTPS outbound** ke: |
||||||
|
|
||||||
|
- `https://agent.gisportal.id` (install + POST laporan) |
||||||
|
|
||||||
|
Split-DNS MikroTik sudah mengarahkan domain ini ke `10.100.1.24` di LAN. |
||||||
|
|
||||||
|
## Opsi parameter (advanced) |
||||||
|
|
||||||
|
Edit baris di `.cmd` jika perlu: |
||||||
|
|
||||||
|
```batch |
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0GeoNetAgent-GPO-Startup.ps1" -ScheduleHours 3 -MaxNetworkWaitSeconds 180 |
||||||
|
``` |
||||||
|
|
||||||
|
## Rollback / uninstall |
||||||
|
|
||||||
|
1. Unlink GPO dari OU |
||||||
|
2. Hapus task: |
||||||
|
|
||||||
|
```cmd |
||||||
|
schtasks /Delete /TN GeoNetAgent-Report /F |
||||||
|
schtasks /Delete /TN GeoNetAgent-Report-Startup /F |
||||||
|
``` |
||||||
|
|
||||||
|
3. Hapus folder: `%ProgramData%\GeoNetAgent` |
||||||
|
|
||||||
|
## Troubleshooting |
||||||
|
|
||||||
|
| Gejala | Cek | |
||||||
|
|--------|-----| |
||||||
|
| Log tidak ada | GPO belum jalan / path script salah / OU salah | |
||||||
|
| `Jaringan tidak reachable` | DNS, firewall, proxy; boot terlalu cepat — naikkan `MaxNetworkWaitSeconds` | |
||||||
|
| VERSION kosong | Install gagal — buka log, coba web install manual Admin | |
||||||
|
| Task tidak ada | Jalankan manual: `Repair` via install Admin sekali | |
||||||
|
| GPO tidak apply | `gpresult /H report.html` pada laptop | |
||||||
|
|
||||||
|
## Alternatif tanpa GPO |
||||||
|
|
||||||
|
Web install manual (2 baris PS Admin) — lihat `agent/install/index.html` atau `agent/README.md`. |
||||||
@ -0,0 +1,209 @@ |
|||||||
|
function Get-GeoNetInstallBaseUrl { |
||||||
|
param([string]$Url) |
||||||
|
return $Url.TrimEnd('/') |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetRemoteJson { |
||||||
|
param([string]$Url) |
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||||
|
return (Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 60).Content | ConvertFrom-Json |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetRemoteFile { |
||||||
|
param([string]$Url, [string]$Destination) |
||||||
|
$parent = Split-Path -Parent $Destination |
||||||
|
if (-not (Test-Path $parent)) { |
||||||
|
New-Item -ItemType Directory -Path $parent -Force | Out-Null |
||||||
|
} |
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||||
|
$lastErr = $null |
||||||
|
for ($try = 1; $try -le 3; $try++) { |
||||||
|
try { |
||||||
|
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -TimeoutSec 90 |
||||||
|
if ((Get-Item $Destination).Length -gt 0) { return } |
||||||
|
} |
||||||
|
catch { |
||||||
|
$lastErr = $_ |
||||||
|
Start-Sleep -Seconds 2 |
||||||
|
} |
||||||
|
} |
||||||
|
if (Get-Command curl.exe -ErrorAction SilentlyContinue) { |
||||||
|
& curl.exe -fsSL --tlsv1.2 $Url -o $Destination |
||||||
|
if ($LASTEXITCODE -eq 0 -and (Test-Path $Destination)) { return } |
||||||
|
} |
||||||
|
throw $lastErr |
||||||
|
} |
||||||
|
|
||||||
|
function Copy-GeoNetAgentPayload { |
||||||
|
param( |
||||||
|
[string]$SourceRoot, |
||||||
|
[string]$DestinationRoot, |
||||||
|
[object]$Manifest |
||||||
|
) |
||||||
|
foreach ($rel in @($Manifest.files)) { |
||||||
|
$src = Join-Path $SourceRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||||
|
$dst = Join-Path $DestinationRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||||
|
if (-not (Test-Path $src)) { |
||||||
|
throw "File sumber tidak ada: $src" |
||||||
|
} |
||||||
|
$dir = Split-Path -Parent $dst |
||||||
|
if (-not (Test-Path $dir)) { |
||||||
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null |
||||||
|
} |
||||||
|
Copy-Item -Path $src -Destination $dst -Force |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Install-GeoNetAgentPayload { |
||||||
|
param( |
||||||
|
[string]$Base, |
||||||
|
[string]$DestinationRoot, |
||||||
|
[object]$Manifest, |
||||||
|
[switch]$Quiet |
||||||
|
) |
||||||
|
foreach ($rel in @(Get-GeoNetManifestFileOrder -Files $Manifest.files)) { |
||||||
|
$url = "$Base/$($rel -replace '\\', '/')" |
||||||
|
$dst = Join-Path $DestinationRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||||
|
if (-not $Quiet) { Write-Host " -> $rel" } |
||||||
|
Get-GeoNetRemoteFile -Url $url -Destination $dst |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetManifestFileOrder { |
||||||
|
param([object[]]$Files) |
||||||
|
@($Files | Sort-Object { |
||||||
|
if ($_ -eq 'UserAgent.ps1') { 'zzz' } else { $_ } |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetLocalAgentVersion { |
||||||
|
param([string]$InstallRoot) |
||||||
|
$versionPath = Join-Path $InstallRoot 'VERSION' |
||||||
|
if (-not (Test-Path $versionPath)) { return $null } |
||||||
|
$text = (Get-Content $versionPath -Raw -ErrorAction SilentlyContinue) |
||||||
|
if ([string]::IsNullOrWhiteSpace($text)) { return $null } |
||||||
|
return $text.Trim() |
||||||
|
} |
||||||
|
|
||||||
|
function Set-GeoNetLocalAgentVersion { |
||||||
|
param( |
||||||
|
[string]$InstallRoot, |
||||||
|
[string]$Version |
||||||
|
) |
||||||
|
$Version.Trim() | Set-Content -Path (Join-Path $InstallRoot 'VERSION') -Encoding ASCII -NoNewline |
||||||
|
} |
||||||
|
|
||||||
|
function Invoke-GeoNetSchtasks { |
||||||
|
param( |
||||||
|
[Parameter(Mandatory)][string[]]$ArgumentList, |
||||||
|
[switch]$IgnoreError |
||||||
|
) |
||||||
|
$prev = $ErrorActionPreference |
||||||
|
$ErrorActionPreference = 'SilentlyContinue' |
||||||
|
$output = & schtasks.exe @ArgumentList 2>&1 |
||||||
|
$code = $LASTEXITCODE |
||||||
|
$ErrorActionPreference = $prev |
||||||
|
if ($code -ne 0 -and -not $IgnoreError) { |
||||||
|
$msg = ($output | Out-String).Trim() |
||||||
|
if ($msg) { throw $msg } |
||||||
|
throw "schtasks gagal (exit $code): schtasks.exe $($ArgumentList -join ' ')" |
||||||
|
} |
||||||
|
return $code |
||||||
|
} |
||||||
|
|
||||||
|
function Register-GeoNetAgentTask { |
||||||
|
param( |
||||||
|
[string]$InstallRoot, |
||||||
|
[int]$Hours = 3 |
||||||
|
) |
||||||
|
$userScript = Join-Path $InstallRoot 'UserAgent.ps1' |
||||||
|
if (-not (Test-Path $userScript)) { |
||||||
|
throw "UserAgent.ps1 tidak ditemukan di $InstallRoot" |
||||||
|
} |
||||||
|
|
||||||
|
$taskArgs = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`"" |
||||||
|
$taskCmd = "powershell.exe $taskArgs" |
||||||
|
$taskNames = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') |
||||||
|
|
||||||
|
foreach ($name in $taskNames) { |
||||||
|
Unregister-ScheduledTask -TaskName $name -Confirm:$false -ErrorAction SilentlyContinue |
||||||
|
Invoke-GeoNetSchtasks -ArgumentList @('/Delete', '/TN', $name, '/F') -IgnoreError | Out-Null |
||||||
|
} |
||||||
|
|
||||||
|
Invoke-GeoNetSchtasks -ArgumentList @( |
||||||
|
'/Create', '/TN', 'GeoNetAgent-Report-Startup', '/TR', $taskCmd, |
||||||
|
'/SC', 'ONSTART', '/RU', 'SYSTEM', '/RL', 'HIGHEST', '/F' |
||||||
|
) | Out-Null |
||||||
|
|
||||||
|
Invoke-GeoNetSchtasks -ArgumentList @( |
||||||
|
'/Create', '/TN', 'GeoNetAgent-Report', '/TR', $taskCmd, |
||||||
|
'/SC', 'HOURLY', '/MO', "$Hours", '/RU', 'SYSTEM', '/RL', 'HIGHEST', '/F' |
||||||
|
) | Out-Null |
||||||
|
} |
||||||
|
|
||||||
|
function Set-GeoNetAgentConfig { |
||||||
|
param( |
||||||
|
[string]$InstallRoot, |
||||||
|
[string]$Base, |
||||||
|
[string]$Token, |
||||||
|
[bool]$UseRemoteConfig |
||||||
|
) |
||||||
|
$configPath = Join-Path $InstallRoot 'config.json' |
||||||
|
|
||||||
|
if ($Token) { |
||||||
|
$cfg = [ordered]@{ |
||||||
|
collector_url = 'https://agent.gisportal.id/api/v1/agent/report' |
||||||
|
agent_token = $Token |
||||||
|
include_software = $true |
||||||
|
max_software = 500 |
||||||
|
timeout_seconds = 120 |
||||||
|
} |
||||||
|
if (Test-Path $configPath) { |
||||||
|
try { |
||||||
|
$existing = Get-Content $configPath -Raw | ConvertFrom-Json |
||||||
|
if ($existing.collector_url) { $cfg.collector_url = $existing.collector_url } |
||||||
|
if ($null -ne $existing.include_software) { $cfg.include_software = [bool]$existing.include_software } |
||||||
|
if ($existing.max_software) { $cfg.max_software = [int]$existing.max_software } |
||||||
|
if ($existing.timeout_seconds) { $cfg.timeout_seconds = [int]$existing.timeout_seconds } |
||||||
|
} |
||||||
|
catch { } |
||||||
|
} |
||||||
|
$cfg | ConvertTo-Json | Set-Content -Path $configPath -Encoding UTF8 |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if ((Test-Path $configPath) -and -not $UseRemoteConfig) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if ($UseRemoteConfig) { |
||||||
|
Get-GeoNetRemoteFile -Url "$Base/config.json" -Destination $configPath |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
throw 'config.json tidak ada. Sediakan -AgentToken atau config.json di URL install.' |
||||||
|
} |
||||||
|
|
||||||
|
function Test-GeoNetAdmin { |
||||||
|
$id = [Security.Principal.WindowsIdentity]::GetCurrent() |
||||||
|
$p = New-Object Security.Principal.WindowsPrincipal($id) |
||||||
|
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetInstallBaseUrlFromConfig { |
||||||
|
param([string]$ConfigPath) |
||||||
|
|
||||||
|
$default = 'https://agent.gisportal.id/install' |
||||||
|
if (-not $ConfigPath -or -not (Test-Path $ConfigPath)) { return $default } |
||||||
|
|
||||||
|
try { |
||||||
|
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json |
||||||
|
if ($cfg.install_url) { return Get-GeoNetInstallBaseUrl -Url $cfg.install_url } |
||||||
|
if ($cfg.collector_url -match '^(https?://[^/]+)') { |
||||||
|
return Get-GeoNetInstallBaseUrl -Url "$($Matches[1])/install" |
||||||
|
} |
||||||
|
} |
||||||
|
catch { } |
||||||
|
|
||||||
|
return $default |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
$formatText = Join-Path $PSScriptRoot 'Format-Text.ps1' |
||||||
|
if (Test-Path $formatText) { . $formatText } |
||||||
|
|
||||||
|
function Convert-GeoNetAdapterRamMb { |
||||||
|
param([AllowNull()]$AdapterRam) |
||||||
|
|
||||||
|
if ($null -eq $AdapterRam -or $AdapterRam -le 0) { return $null } |
||||||
|
|
||||||
|
try { |
||||||
|
$mb = [math]::Round([decimal]$AdapterRam / 1MB, 0) |
||||||
|
if ($mb -le 0 -or $mb -gt 131072) { return $null } |
||||||
|
return [int]$mb |
||||||
|
} catch { |
||||||
|
return $null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function Get-GeoNetGpuInfo { |
||||||
|
$controllers = Invoke-GeoNetSafe { |
||||||
|
Get-CimInstance Win32_VideoController -ErrorAction Stop | |
||||||
|
Where-Object { |
||||||
|
$_.Name -and $_.Name -notmatch 'Microsoft Basic|Remote|Sage Display|Display Manager' |
||||||
|
} |
||||||
|
} -Default @() |
||||||
|
|
||||||
|
$gpus = @($controllers | ForEach-Object { |
||||||
|
$name = Convert-GeoNetOptionalString -Value $_.Name |
||||||
|
if (-not $name) { return } |
||||||
|
|
||||||
|
[pscustomobject]@{ |
||||||
|
name = $name |
||||||
|
dedicated_memory_mb = Convert-GeoNetAdapterRamMb -AdapterRam $_.AdapterRAM |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
@($gpus | Sort-Object { |
||||||
|
if ($null -ne $_.dedicated_memory_mb) { $_.dedicated_memory_mb } else { -1 } |
||||||
|
} -Descending) |
||||||
|
} |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
$installCore = Join-Path $PSScriptRoot 'Agent-InstallCore.ps1' |
||||||
|
if (Test-Path $installCore) { . $installCore } |
||||||
|
|
||||||
|
function Update-GeoNetAgentIfNeeded { |
||||||
|
<# |
||||||
|
.SYNOPSIS |
||||||
|
Cek manifest.json di server vs VERSION lokal; unduh paket jika beda. |
||||||
|
Gagal update tidak menghentikan pemanggil (mis. kirim laporan tetap jalan). |
||||||
|
#> |
||||||
|
[CmdletBinding()] |
||||||
|
param( |
||||||
|
[Parameter(Mandatory)] |
||||||
|
[string]$InstallRoot, |
||||||
|
[string]$BaseUrl, |
||||||
|
[string]$ConfigPath, |
||||||
|
[int]$ScheduleHours = 3, |
||||||
|
[switch]$Quiet |
||||||
|
) |
||||||
|
|
||||||
|
$result = [ordered]@{ |
||||||
|
Updated = $false |
||||||
|
From = $null |
||||||
|
To = $null |
||||||
|
Error = $null |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
if (-not $BaseUrl) { |
||||||
|
$BaseUrl = Get-GeoNetInstallBaseUrlFromConfig -ConfigPath $ConfigPath |
||||||
|
} |
||||||
|
$base = Get-GeoNetInstallBaseUrl -Url $BaseUrl |
||||||
|
|
||||||
|
$manifest = Get-GeoNetRemoteJson -Url "$base/manifest.json" |
||||||
|
$remoteVersion = [string]$manifest.version |
||||||
|
if ([string]::IsNullOrWhiteSpace($remoteVersion)) { |
||||||
|
throw 'manifest.json server tidak memuat version.' |
||||||
|
} |
||||||
|
$remoteVersion = $remoteVersion.Trim() |
||||||
|
|
||||||
|
$localVersion = Get-GeoNetLocalAgentVersion -InstallRoot $InstallRoot |
||||||
|
$result.From = $localVersion |
||||||
|
|
||||||
|
if ($localVersion -eq $remoteVersion) { |
||||||
|
return [pscustomobject]$result |
||||||
|
} |
||||||
|
|
||||||
|
if (-not $Quiet) { |
||||||
|
Write-Host "GeoNetAgent auto-update: $localVersion -> $remoteVersion" -ForegroundColor Cyan |
||||||
|
} |
||||||
|
|
||||||
|
Install-GeoNetAgentPayload -Base $base -DestinationRoot $InstallRoot -Manifest $manifest -Quiet:$Quiet |
||||||
|
Set-GeoNetLocalAgentVersion -InstallRoot $InstallRoot -Version $remoteVersion |
||||||
|
|
||||||
|
try { |
||||||
|
Register-GeoNetAgentTask -InstallRoot $InstallRoot -Hours $ScheduleHours |
||||||
|
} |
||||||
|
catch { |
||||||
|
if (-not $Quiet) { |
||||||
|
Write-Warning "Auto-update: file terbaru, tetapi refresh Scheduled Task gagal: $_" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$result.Updated = $true |
||||||
|
$result.To = $remoteVersion |
||||||
|
} |
||||||
|
catch { |
||||||
|
$result.Error = $_.Exception.Message |
||||||
|
if (-not $Quiet) { |
||||||
|
Write-Warning "GeoNetAgent auto-update dilewati: $($result.Error)" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return [pscustomobject]$result |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
-- Migration 002: physical disk model on asset_disks |
||||||
|
ALTER TABLE asset_disks |
||||||
|
ADD COLUMN IF NOT EXISTS model varchar(100); |
||||||
@ -0,0 +1,320 @@ |
|||||||
|
{ |
||||||
|
"schema_version": "1.0", |
||||||
|
"agent_version": "0.1.7-phase0", |
||||||
|
"collected_at": "2026-06-18T11:25:23+07:00", |
||||||
|
"hostname": "DESKTOP-RRJ9G01", |
||||||
|
"machine_id": "3709baa5-5265-4e4d-9a81-fb8c8927bb3b", |
||||||
|
"smbios_uuid": "03D502E0-045E-0550-8E06-BA0700080009", |
||||||
|
"serial_number": null, |
||||||
|
"asset_type": "endpoint", |
||||||
|
"logged_in_user": "DESKTOP-RRJ9G01\\admin", |
||||||
|
"system": { |
||||||
|
"manufacturer": "Gigabyte Technology Co., Ltd.", |
||||||
|
"model": "H81M-DS2", |
||||||
|
"bios_version": "F2", |
||||||
|
"os_name": "Microsoft Windows 11 Pro", |
||||||
|
"os_version": "10.0", |
||||||
|
"os_build": "10.0.26200 (26200)", |
||||||
|
"cpu_model": "Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz", |
||||||
|
"ram_gb": 15.9, |
||||||
|
"gpu_model": "NVIDIA GeForce GT 730", |
||||||
|
"last_boot_at": "2026-06-18T10:41:02+07:00" |
||||||
|
}, |
||||||
|
"gpus": [ |
||||||
|
{ |
||||||
|
"name": "NVIDIA GeForce GT 730", |
||||||
|
"dedicated_memory_mb": 2048 |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Intel(R) HD Graphics 4600", |
||||||
|
"dedicated_memory_mb": 1024 |
||||||
|
} |
||||||
|
], |
||||||
|
"disks": [ |
||||||
|
{ |
||||||
|
"drive_letter": "C:", |
||||||
|
"total_gb": 188.7, |
||||||
|
"free_gb": 4.6, |
||||||
|
"pct_used": 97.6, |
||||||
|
"file_system": "NTFS", |
||||||
|
"model": "ADATA SU650" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"drive_letter": "D:", |
||||||
|
"total_gb": 256.5, |
||||||
|
"free_gb": 68.5, |
||||||
|
"pct_used": 73.3, |
||||||
|
"file_system": "NTFS", |
||||||
|
"model": "ADATA SU650" |
||||||
|
} |
||||||
|
], |
||||||
|
"memory_modules": [ |
||||||
|
{ |
||||||
|
"slot": "ChannelA-DIMM0", |
||||||
|
"bank_label": "BANK 0", |
||||||
|
"capacity_gb": 8, |
||||||
|
"speed_mhz": 1600, |
||||||
|
"manufacturer": "0000", |
||||||
|
"part_number": "", |
||||||
|
"form_factor": "tsop", |
||||||
|
"memory_type": 24 |
||||||
|
}, |
||||||
|
{ |
||||||
|
"slot": "ChannelB-DIMM0", |
||||||
|
"bank_label": "BANK 2", |
||||||
|
"capacity_gb": 8, |
||||||
|
"speed_mhz": 1600, |
||||||
|
"manufacturer": "0000", |
||||||
|
"part_number": "", |
||||||
|
"form_factor": "tsop", |
||||||
|
"memory_type": 24 |
||||||
|
} |
||||||
|
], |
||||||
|
"memory_summary": { |
||||||
|
"total_gb": 16, |
||||||
|
"module_count": 2, |
||||||
|
"slot_count": 2, |
||||||
|
"empty_slots": 0, |
||||||
|
"layout": "2x8GB", |
||||||
|
"configuration": "2x8GB tsop", |
||||||
|
"upgradeable": false |
||||||
|
}, |
||||||
|
"network": [ |
||||||
|
{ |
||||||
|
"adapter_name": "Realtek(R) PCI(e) Ethernet Controller", |
||||||
|
"mac_address": "E0:D5:5E:50:8E:BA", |
||||||
|
"ip_address": "192.168.0.21", |
||||||
|
"adapter_kind": "physical", |
||||||
|
"status": "Up", |
||||||
|
"is_primary": true, |
||||||
|
"is_built_in": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "Fortinet SSL VPN Virtual Ethernet Adapter", |
||||||
|
"mac_address": "00:09:0F:AA:00:01", |
||||||
|
"ip_address": null, |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Not Present", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "Fortinet Virtual Ethernet Adapter (NDIS 6.30)", |
||||||
|
"mac_address": "00:09:0F:FE:00:01", |
||||||
|
"ip_address": null, |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Disconnected", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "Hyper-V Virtual Ethernet Adapter", |
||||||
|
"mac_address": "00:15:5D:F1:DB:09", |
||||||
|
"ip_address": "172.27.176.1", |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Up", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "OpenVPN Data Channel Offload", |
||||||
|
"mac_address": null, |
||||||
|
"ip_address": null, |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Disconnected", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "PANGP Virtual Ethernet Adapter Secure #2", |
||||||
|
"mac_address": "02:50:41:00:00:01", |
||||||
|
"ip_address": null, |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Disabled", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
}, |
||||||
|
{ |
||||||
|
"adapter_name": "TAP-Windows Adapter V9 for OpenVPN Connect", |
||||||
|
"mac_address": "00:FF:E0:E1:4D:4D", |
||||||
|
"ip_address": null, |
||||||
|
"adapter_kind": "virtual", |
||||||
|
"status": "Disconnected", |
||||||
|
"is_primary": false, |
||||||
|
"is_built_in": null |
||||||
|
} |
||||||
|
], |
||||||
|
"asset_identity": { |
||||||
|
"primary_physical_mac": "E0:D5:5E:50:8E:BA", |
||||||
|
"primary_physical_adapter": "Realtek(R) PCI(e) Ethernet Controller", |
||||||
|
"primary_mac_built_in": true, |
||||||
|
"primary_mac_source": "primary_route_built_in_physical", |
||||||
|
"merge_key": "mac:E0:D5:5E:50:8E:BA", |
||||||
|
"recommended_match_order": [ |
||||||
|
"serial_number", |
||||||
|
"smbios_uuid", |
||||||
|
"primary_physical_mac", |
||||||
|
"machine_id", |
||||||
|
"hostname" |
||||||
|
] |
||||||
|
}, |
||||||
|
"peripherals": [ |
||||||
|
{ |
||||||
|
"name": "E1942", |
||||||
|
"device_class": "Monitor", |
||||||
|
"manufacturer": "GSM", |
||||||
|
"connection": "display", |
||||||
|
"device_role": "display", |
||||||
|
"vid": null, |
||||||
|
"pid": null, |
||||||
|
"pnp_device_id": "DISPLAY\\GSM4C09\\5\u0026316DE597\u00260\u0026UID4865", |
||||||
|
"device_serial": "211INNG5R639", |
||||||
|
"edid_product": "GSM4C09", |
||||||
|
"resolution": "1366x768", |
||||||
|
"resolution_width": 1366, |
||||||
|
"resolution_height": 768, |
||||||
|
"refresh_hz": 60, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "USB Input Device", |
||||||
|
"device_class": "HIDClass", |
||||||
|
"manufacturer": "(Standard system devices)", |
||||||
|
"connection": "usb", |
||||||
|
"device_role": "peripheral", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_01\\8\u0026104DB858\u00260\u00260001", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "USB Input Device", |
||||||
|
"device_class": "HIDClass", |
||||||
|
"manufacturer": "(Standard system devices)", |
||||||
|
"connection": "usb", |
||||||
|
"device_role": "peripheral", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_00\\8\u0026104DB858\u00260\u00260000", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HID-compliant consumer control device", |
||||||
|
"device_class": "HIDClass", |
||||||
|
"manufacturer": "Microsoft", |
||||||
|
"connection": "usb_hid", |
||||||
|
"device_role": "input", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL03\\9\u00262CF1CDFF\u00260\u00260002", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HID-compliant system controller", |
||||||
|
"device_class": "HIDClass", |
||||||
|
"manufacturer": "(Standard system devices)", |
||||||
|
"connection": "usb_hid", |
||||||
|
"device_role": "input", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL02\\9\u00262CF1CDFF\u00260\u00260001", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HID-compliant vendor-defined device", |
||||||
|
"device_class": "HIDClass", |
||||||
|
"manufacturer": "(Standard system devices)", |
||||||
|
"connection": "usb_hid", |
||||||
|
"device_role": "input", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL04\\9\u00262CF1CDFF\u00260\u00260003", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HID Keyboard Device", |
||||||
|
"device_class": "Keyboard", |
||||||
|
"manufacturer": "(Standard keyboards)", |
||||||
|
"connection": "usb_hid", |
||||||
|
"device_role": "input", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_00\\9\u002638016031\u00260\u00260000", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HID-compliant mouse", |
||||||
|
"device_class": "Mouse", |
||||||
|
"manufacturer": "Microsoft", |
||||||
|
"connection": "usb_hid", |
||||||
|
"device_role": "input", |
||||||
|
"vid": "3151", |
||||||
|
"pid": "3022", |
||||||
|
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL01\\9\u00262CF1CDFF\u00260\u00260000", |
||||||
|
"device_serial": null, |
||||||
|
"edid_product": null, |
||||||
|
"resolution": null, |
||||||
|
"resolution_width": null, |
||||||
|
"resolution_height": null, |
||||||
|
"refresh_hz": null, |
||||||
|
"status": "OK", |
||||||
|
"is_external": true |
||||||
|
} |
||||||
|
], |
||||||
|
"health": { |
||||||
|
"bsod_count_30d": 6, |
||||||
|
"pending_reboot": false, |
||||||
|
"last_patch_installed": "2026-02-27" |
||||||
|
}, |
||||||
|
"software": [ |
||||||
|
|
||||||
|
], |
||||||
|
"metadata": { |
||||||
|
"collect_mode": "phase0-local", |
||||||
|
"software_included": false, |
||||||
|
"software_count": 0 |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue