gonsutrijayautama / gonsu-one-sdk
SDK lisensi GONSU One untuk produk PHP
Package info
github.com/gonsutrijayautama/gonsu-one-sdk-php
pkg:composer/gonsutrijayautama/gonsu-one-sdk
Requires
- php: ^8.2
- ext-json: *
- ext-sodium: *
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Untuk tim yang membangun produk untuk dijual di GONSU One.
SDK ini dipasang di dalam produk Anda. Ia menjawab satu pertanyaan: apa yang boleh dijalankan pemasangan ini, dan sampai kapan.
Yang TIDAK perlu Anda kerjakan sendiri: menerbitkan pemasangan, menyerahkan
token aktivasi, atau menerapkan lisensi ketika ada yang berlangganan. Platform
yang mengurusnya — produk Anda cukup membaca status().
composer require gonsutrijayautama/gonsu-one-sdk
PHP 8.2+, butuh ext-sodium dan ext-json. Nol dependency runtime — SDK ini
tidak menarik Guzzle, PSR-7, atau apa pun ke dalam produk Anda.
Cara kerja
GONSU menerbitkan lease: pernyataan bertanda tangan Ed25519 yang membawa salinan hak pakai beserta masa berlakunya. SDK menyimpannya ke disk dan memverifikasinya terhadap kunci publik GONSU yang ada di dalam kode produk Anda.
Akibatnya, pemasangan yang tidak dapat menghubungi GONSU tetap tahu apa yang boleh dijalankannya — dan tetap dapat menolak lease yang diubah orang.
Dua sumbu keputusan, sengaja tidak digabung:
| arti | |
|---|---|
$status->lease->granted |
langganan sedang memberi hak pakai |
$status->state |
kesegaran lease: Active, Grace, Expired, Unknown |
$status->allowed() menggabungkan keduanya untuk produk yang hanya ingin satu
jawaban. Masa tenggang termasuk diizinkan: mematikan pelanggan pada detik lease
kedaluwarsa adalah reaksi yang tidak dapat dibatalkan terhadap sesuatu yang
paling sering hanyalah gangguan jaringan.
Pemakaian
use Gonsu\One\License; use Gonsu\One\VendorKey; $license = License::open( baseUrl: getenv('GONSU_BASE_URL') ?: 'https://api.gonsu.cloud', // Di cloud, GONSU mengisinya sendiri lewat Secret aplikasi — tidak ada yang // menempelkannya dengan tangan. Di self-host, installer yang menuliskannya // ke .env dari token yang diberikan Portal. // // Dari sudut pandang produk, keduanya sama: baca environment. installationId: getenv('GONSU_INSTALLATION_ID'), stateDir: '/var/lib/produk-anda/lisensi', // wajib bertahan antar restart vendorKeys: VendorKey::fromCommaSeparated(KUNCI_GONSU), version: '1.4.0', platform: PHP_OS_FAMILY, ); // Sekali seumur pemasangan, dengan token aktivasi dari Portal. $license->activate(getenv('GONSU_ACTIVATION_TOKEN')); // Di dalam controller mana pun — tidak menyentuh jaringan. $status = $license->status(); if (!$status->allowed()) { abort(403, 'lisensi tidak aktif'); } if ($status->feature('backup.enabled')) { /* ... */ } [$batas, $tanpaBatas] = $status->limit('users.max'); if (!$tanpaBatas && $jumlah >= $batas) { abort(402, 'kuota pengguna habis'); }
open() tidak menyentuh jaringan. Produk yang dimulai saat GONSU tidak dapat
dihubungi tetap dapat berjalan dari lease di disk — itulah seluruh gunanya lease
disimpan.
Biayanya dua kali baca berkas kecil dan satu verifikasi Ed25519 (puluhan mikrodetik). Aman dipanggil sekali per request di PHP-FPM; tidak perlu cache.
Menjaga lease tetap segar
PHP tidak punya proses latar seperti go license.Run(ctx). Panggil refresh()
dari penjadwal, sekali dalam satu jam:
// Laravel — routes/console.php Schedule::call(fn () => app(License::class)->refresh())->hourly();
# Tanpa framework 17 * * * * www-data php /srv/produk/bin/license-refresh.php
Kegagalan refresh() tidak membatalkan lease yang sudah dipegang. Ia
melempar ApiException; catat, lalu lanjutkan. Gangguan lima menit di sisi
GONSU tidak boleh menghentikan setiap pelanggan sekaligus.
Saat produk dicopot, panggil deactivate(). Tanpa itu, pemasangan yang
sudah tidak ada tetap terhitung aktif — dan pada produk yang dijual per
pemasangan, pelanggan membayar sesuatu yang sudah ia hapus.
Dari mana kunci publik GONSU didapat
Ambil dari OpenBao — sumber yang sama dengan pipeline GONSU sendiri:
curl -sS -H "X-Vault-Token: $GONSU_ONE_OPENBAO_TOKEN" \ "$GONSU_ONE_OPENBAO_ADDRESS/v1/transit/keys/license-signing-v1" | python3 -c ' import json, sys data = json.load(sys.stdin)["data"] minimum = int(data.get("min_decryption_version") or 1) versi = sorted((int(v) for v in data["keys"] if int(v) >= minimum), reverse=True) print(",".join(data["keys"][str(v)]["public_key"] for v in versi))'
Ambil SELURUH versi, dipisah koma — bukan yang terbaru saja. Alasannya di bagian "Rotasi kunci"; mengabaikannya berarti produk Anda berhenti memverifikasi pada hari GONSU berpindah kunci.
Taruh di kode, bukan di .env
// config/gonsu.php — ikut commit, ikut rilis const KUNCI_GONSU = 'MCowBQYDK2Vw...,MCowBQYDK2Vw...';
Produk PHP dikirim sebagai kode sumber, jadi tidak ada yang benar-benar tidak dapat diubah di server pelanggan sendiri — itu kenyataan, bukan kelemahan SDK ini. Yang dijaga tanda tangan adalah berkas lease yang diubah orang, dan itu tetap dijaga.
Yang tetap penting: jangan pindahkan kuncinya ke .env. .env adalah berkas
yang rutin disunting, disalin, dan di-backup operator; memindahkan kunci ke sana
mengubah "menyunting kode produk" menjadi "menyunting konfigurasi", dan yang
kedua terjadi setiap hari.
TLS wajib, kecuali ke diri sendiri
open() menolak http:// ke host mana pun selain loopback:
https://api.gonsu.cloud ✓
http://localhost:8091 ✓ pengembangan
http://127.0.0.1:8091 ✓
http://api.gonsu.cloud ✗ InvalidArgumentException
SDK tidak punya — dan tidak boleh punya — gagasan tentang "production"; ia hanya tahu alamat yang Anda berikan. Yang dapat diputuskannya sendiri adalah aturan yang tidak butuh konfigurasi: teks polos hanya boleh menuju diri sendiri.
Tidak ada opsi "izinkan tidak aman", dan itu disengaja. Flag yang harus diingat seseorang adalah flag yang menyala di production justru karena ia menyala di laptop lebih dulu, lalu ikut tersalin.
Yang dijaga bukan kerahasiaan lease — ia bertanda tangan dan sudah tahan diubah. Yang dijaga adalah token aktivasi yang melintas di badan request. Jaringan di dalam cluster pun bukan alasan mengirimkannya sebagai teks polos.
Rotasi kunci — baca ini sebelum rilis pertama
VendorKey menampung beberapa kunci, dan itu bukan kelebihan melainkan
keharusan.
Kunci penandatangan GONSU akan dirotasi suatu hari — terjadwal atau karena insiden. Produk yang hanya memegang satu kunci berhenti memverifikasi apa pun pada detik rotasi, dan sebagian pemasangan berada di server pelanggan yang tidak dapat diperbarui hari itu juga.
Urutan rotasi yang benar, dan urutannya menentukan:
- GONSU menerbitkan kunci baru, masih menandatangani dengan yang lama;
- produk dirilis ulang membawa kunci baru bersama yang lama;
- seluruh pemasangan menerima versi itu;
- baru GONSU mulai menandatangani dengan kunci baru;
- kunci lama dicabut paling akhir.
Sebuah lease diterima bila salah satu kunci yang Anda bawa memverifikasinya. Seluruhnya kunci GONSU, jadi tidak ada yang melemah — yang membuktikan tetap tanda tangannya.
Jam yang meleset
Parameter skewSeconds (bawaan 300) memberi kelonggaran saat menilai
kedaluwarsa. Arahnya hanya memperpanjang, tidak pernah memperpendek.
Sengaja begitu: kesalahan karena longgar berarti produk melayani beberapa menit lebih lama; kesalahan karena ketat berarti pelanggan yang membayar mendadak berhenti dilayani karena jam servernya maju. Di server yang tidak pernah menyentuh NTP — dan yang air-gapped memang tidak — jam melenceng tanpa ada yang menyadari.
Versi skema lease
$lease->schemaVersion menyatakan bentuk lease. Kontraknya mengikat GONSU,
bukan Anda: naiknya versi skema wajib tetap dapat dibaca pembaca lama. GONSU
boleh menambah field; ia tidak boleh mengubah arti field yang sudah ada, dan
tidak boleh menambahkan pembatasan yang hanya dipahami pembaca baru.
Karena itu SDK yang menemukan versi lebih tinggi tidak berhenti melayani — ia
menandainya lewat $status->schemaAhead. Catat itu sekali di log Anda sebagai
pertanda SDK layak diperbarui; jangan tunjukkan kepada pengguna akhir, dan
jangan jadikan alasan berhenti.
Server tanpa internet (enterprise offline)
Untuk pemasangan yang tidak pernah dapat menghubungi GONSU sama sekali:
// 1. Di server pelanggan — kunci privat lahir di sini dan tidak pernah keluar. echo $license->publicKey(), PHP_EOL; // kirim ini ke GONSU lewat jalur apa pun // 2. GONSU menerbitkan lisensi yang terikat pada kunci itu. // 3. Kembali di server pelanggan, tanpa jaringan sama sekali: $signed = SignedLease::fromArray(json_decode(file_get_contents($berkas), true)); $license->installOffline($signed); // melempar LeaseException bila ditolak
LeaseException::$reason menyebut sebabnya: signature (berkasnya berubah),
other_machine (untuk mesin lain), unbound (lisensi offline tanpa pengikatan).
Lisensi offline terikat pada satu mesin dan tidak dapat dicabut — hanya masa berlakunya yang menghentikannya.
HTTP client Anda sendiri
SDK memakai cURL secara bawaan. Kalau produk Anda sudah punya HTTP client sendiri — Guzzle, atau client bawaan Laravel — tuliskan satu adapter:
use Gonsu\One\Http\Transport; use Illuminate\Support\Facades\Http; final class LaravelTransport implements Transport { public function send(string $method, string $url, array $headers, string $body): array { $r = Http::withHeaders($headers)->withBody($body)->send($method, $url); return [$r->status(), $r->body()]; } }
Antarmukanya milik sendiri, bukan PSR-18 — psr/http-client menyeret psr/http-message beserta implementasinya, dan SDK ini tidak menarik dependency apa pun ke dalam produk Anda.
Menguji produk Anda tanpa GONSU
Clock::statusAt() publik justru untuk ini: susun Lease apa pun, lalu nilai
statusnya pada waktu apa pun.
$lease = Lease::fromArray([ 'granted' => true, 'plan_code' => 'pro', 'entries' => [['key' => 'users.max', 'integer' => 25]], 'expires_at' => '2026-10-01T00:00:00Z', 'grace_until' => '2026-10-08T00:00:00Z', ]); $status = Clock::statusAt($lease, new DateTimeImmutable('2026-09-30T00:00:00Z'));
Saran yang menghemat banyak waktu kemudian: jangan bergantung pada License
di dalam kode bisnis Anda. Definisikan interface sempit milik Anda sendiri —
biasanya cukup satu method — dan biarkan License memenuhinya:
interface Lisensi { public function status(): \Gonsu\One\Status; }
Dengan begitu seluruh kode Anda dapat diuji tanpa berkas, tanpa kunci, dan tanpa GONSU.
Dari mana nilainya datang
Produk Anda selalu membaca environment. Yang berbeda hanyalah siapa yang mengisinya, dan itu bukan urusan produk:
| Cloud (GONSU yang memasang) | Self-host | |
|---|---|---|
GONSU_INSTALLATION_ID |
Secret aplikasi, dibuat GONSU saat deploy | installer menulis ke .env |
GONSU_BASE_URL |
Secret aplikasi, alamat DALAM cluster | .env |
GONSU_ACTIVATION_TOKEN |
Secret aplikasi, hanya saat memang dibutuhkan | dari Portal, sekali pakai |
GONSU_STATE_DIR |
volume yang bertahan, disiapkan GONSU | direktori di host |
| kunci publik GONSU | di dalam kode produk, tidak pernah disuntikkan | sama |
Karena itu satu rilis berjalan di kedua mode tanpa jalur kode yang berbeda — dan itu memang tujuannya: jalur yang hanya dipakai satu mode adalah jalur yang tidak pernah teruji.
Yang perlu diketahui operator
stateDirberisi kunci privat pemasangan (installation.key, 0600) dan cache lease. Ia harus bertahan antar restart; kalau tidak, pemasangan kehilangan identitasnya setiap kali produk dimulai ulang. Proses PHP-FPM harus dapat menulis ke sana.- Jangan menyalin
stateDirke mesin lain. Lease terikat pada satuinstallation_iddan SDK menolak lease milik pemasangan lain. - Pencabutan lisensi berlaku ketika lease habis, bukan seketika. Dengan angka bawaan GONSU, jaraknya sampai 4 hari (masa berlaku 24 jam + tenggang 3 hari).
deactivate()melepaskan pemasangan di GONSU dan membuang lease lokalnya. Lease lokal dibuang meskipun GONSU tidak dapat dihubungi: yang diputuskan pelanggan adalah mencopot, dan jaringan yang putus tidak membatalkannya.- Lease di disk yang gagal diverifikasi tidak dipakai dan tidak dihapus — kalau ia diubah orang, berkasnya adalah barang bukti.
Peran pengguna tidak ada di sini
GONSU tidak pernah mengetahui peran di dalam produk Anda, dan SDK ini tidak menyediakan tempat untuk menyimpannya. Yang GONSU sebut adalah ORANGNYA — untuk menetapkan administrator pertama — bukan perannya. Produk Anda yang memutuskan orang itu menjadi apa di dalamnya.
Satu kontrak, banyak bahasa
testdata/contract.json bukan fixture buatan tangan: ia dihasilkan dari kode
server GONSU, dan SDK Go membaca berkas yang sama. Ia mengunci tiga hal — byte
yang ditandatangani, nama field yang dikirim, dan keputusan yang diambil atas
sebuah lease — sehingga perubahan pada salah satunya memerahkan test di kedua
SDK sekaligus, di CI, sebelum sampai ke produk siapa pun.
Lisensi
Bukan perangkat lunak sumber terbuka. Kode sumbernya dipublikasikan agar dapat diperiksa dan diambil dengan Composer — bukan agar dapat dipakai bebas.
Perkakas internal untuk tim yang membangun produk bagi platform GONSU One. Pembeli produk tersebut menerimanya sebagai bagian produk dan tidak membutuhkan lisensi tersendiri. Selengkapnya di LICENSE.
Hak Cipta (c) 2026 PT Gonsu Trijaya Utama.