Search by

gonsutrijayautama / gonsu-one-sdk

gonsutrijayautama

SDK lisensi GONSU One untuk produk PHP

Package info

github.com/gonsutrijayautama/gonsu-one-sdk-php

pkg:composer/gonsutrijayautama/gonsu-one-sdk

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-09-07 02:17 UTC

This package is auto-updated.

Last update: 2026-09-09 10:19:15 UTC


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:

  1. GONSU menerbitkan kunci baru, masih menandatangani dengan yang lama;
  2. produk dirilis ulang membawa kunci baru bersama yang lama;
  3. seluruh pemasangan menerima versi itu;
  4. baru GONSU mulai menandatangani dengan kunci baru;
  5. 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

  • stateDir berisi 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 stateDir ke mesin lain. Lease terikat pada satu installation_id dan 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.