Skip to content
Blog Programming

Bagaimana Saya Memindahkan Antarmuka Keluar dari Mesin Pinjaman

By Romi Fadli, S.Kom. 18 min read Tooling & Automation
On this page
Before: one Colab runtime holds both the interface and the worker. After: the interface on the laptop, the worker alone in Colab, both reaching shared storage for data and a small broker for triggers

Pendahuluan

A. GPU pinjaman, dan halaman yang ikut terbawa

GPU laptop saya punya video memory 4 GB, dan itu tak cukup untuk model di balik beberapa project saya: pembuatan gambar, konversi dokumen, dan speech-to-text. Jadi bagian berat masing-masing berjalan di runtime Google Colab dengan T4 dan sekitar 15 GB VRAM — mesin yang dipinjam selama satu sesi.

Tiap project itu butuh antarmuka. Harus ada tempat saya bisa mengunggah bahan, memulai pekerjaan, memantau kemajuan, dan melihat hasil. Tempat paling alami untuk antarmuka itu ada tepat di sebelah pekerjaannya, di dalam runtime yang sama: web server kecil di dalam notebook yang menyajikan satu halaman, dengan proxy port milik Colab sendiri mengubahnya jadi URL yang bisa saya buka di browser. Ketiga project dimulai dengan cara itu. Tak ada yang salah dengan pilihan itu sebagai permulaan; ia pilihan yang lazim dan cepat, dan ia membuktikan bahwa pekerjaannya bisa dikerjakan sama sekali.

Cara itu bekerja, dan untuk versi pertama ia sulit dikalahkan. Tak ada yang perlu dipasang maupun di-hosting, dan antarmukanya bisa memanggil model langsung karena hidup di proses yang sama. Tapi segala sesuatu tentangnya ikut dipinjam bersama GPU-nya. Ketika sesi berakhir, halamannya ikut berakhir. Tiap runtime baru menghasilkan URL baru. Dan susunannya sendiri — menyajikan aplikasi web dari dalam runtime Colab lewat proxy-nya — ternyata tak disambut baik oleh Colab. Bahkan untuk melihat hasil kemarin, saya harus menyalakan sesi GPU lebih dulu, menghabiskan kuota cuma untuk membuka sebuah halaman.

Tantangannya: memindahkan antarmuka keluar dari mesin pinjaman — supaya ia tak lagi mati bersama runtime maupun bergantung pada apa yang diizinkan Colab — sementara runtime tetap mengerjakan pekerjaan GPU.

Gampang dirumuskan, canggung dibangun. Memindahkan halaman itu sederhana. Yang tidak sederhana adalah segala yang selama ini dikerjakan web server di tengah tanpa ada yang menyadarinya: menjawab request, memegang state pekerjaan, menerjemahkan error jadi respons, dan menjadi satu-satunya tempat yang disepakati kedua pihak sebagai yang benar. Singkirkan server-nya, dan tiap pekerjaan itu harus pindah ke tempat lain. Tak satu pun ikut lenyap bersama server; semuanya cuma kehilangan rumah, dan tiap satunya perlu rumah baru yang dipilih dengan sadar.

B. Apa yang berubah ketika tak ada server di tengah

Susunan yang dituju project-project itu punya tiga bagian, dan tak satu pun web server. Runtime jadi pekerja murni: memuat model, menunggu pekerjaan, mengerjakannya, lalu menulis hasilnya keluar. Antarmuka berjalan di laptop, di browser. Di antara keduanya ada dua layanan yang sama-sama dihubungi keluar oleh kedua pihak: object storage, yang memegang tiap file dan tiap record, dan message broker, yang mengantar pemberitahuan singkat — "mulai yang ini", "yang itu sudah selesai" — di antara keduanya.

Itu menyingkirkan server-nya, dan bersamanya ikut hilang yang dulu menjamin kedua pihak sepakat. Ketika server menjawab satu request, dialah yang menentukan arti jawaban itu. Tanpa server, antarmuka dan worker adalah dua codebase terpisah, dalam dua bahasa, yang harus sepakat secara mandiri soal ke mana file ditaruh, seperti apa rupa item yang selesai, topic mana membawa pesan apa, dan siapa sumber kebenaran ketika keduanya berselisih. Tak ada yang menegakkan kesepakatan itu selain kesepakatan itu sendiri. Worker-nya Python di dalam notebook; antarmukanya TypeScript di dalam browser. Keduanya tak berbagi code, tak berbagi proses, dan tak punya compiler bersama yang bisa menyadari ketidakcocokan di antara mereka. Kalau keduanya berselisih, tak ada yang berteriak; yang ada cuma pekerjaan yang tak kunjung tampak selesai.

Jadi pekerjaan memindahkan antarmuka keluar sebagian besar adalah pekerjaan membuat kesepakatan itu tertulis, bisa diuji, dan sulit dipatahkan — serta menyadari pekerjaan mana yang lebih baik tak dikerjakan di sisi mana pun. Imbalannya adalah antarmuka yang selalu ada, entah ada runtime yang sedang hidup di belakangnya atau tidak. Membuka hasil kemarin cukup dengan membuka browser; menyalakan GPU jadi keputusan tersendiri, bukan syarat untuk melihat apa pun.

C. Delapan hambatan yang menghalangi

Antarmuka pertama hidup di dalam runtime. Itulah cara paling sederhana yang bekerja, dan ia mengikat umur halaman pada umur sesi pinjaman.

Proxy itu sendiri sumber risikonya. Menyajikan aplikasi web lewat proxy port runtime mengundang peringatan dan pembatasan dari Colab, dan bisa membahayakan akunnya. Risiko semacam itu tak bisa ditawar dengan kode yang lebih rapi, sebab yang dipersoalkan bukan kodenya melainkan tempat ia berjalan.

Runtime harus jadi pekerja murni. Mengeluarkan antarmuka berarti membuang satu web framework, satu halaman, dan delapan belas endpoint dari notebook, tanpa mengusik code yang benar-benar membuat gambar, dokumen, atau transkrip.

Tanpa server, harus ada yang lain yang menyajikan data. Antarmuka tetap butuh tiap gambar dan record, dan saluran pemberitahuan adalah tempat yang keliru untuk mengangkutnya.

Tanpa server, kontrak cuma kesepakatan. Dua codebase dalam dua bahasa harus menghitung nama folder dan topic yang sama, dan ketidakcocokan gagal tanpa suara. Yang tampak dari luar cuma worker yang seolah tak pernah menjawab.

Perintah wajib selamat dari pesan yang hilang. Pemberitahuan bisa jatuh di jalan. Permintaan yang cuma ada di dalam pemberitahuan itu ikut jatuh bersamanya.

Pekerjaan yang dipindah ke browser bisa mendarat di tempat yang salah. Menyiapkan media di browser tampak seperti cara meringankan runtime. Ia menggantung, dan perbaikannya justru berhenti mengerjakannya sama sekali.

Kadang antarmuka terbaik adalah tak ada. Untuk pekerjaan singkat, seluruh susunan antarmuka, storage, dan broker adalah mesin yang lebih besar dari yang pantas bagi pekerjaan itu.

Urutannya mengikuti perpindahan itu sendiri: kenapa antarmukanya harus pergi, jadi apa runtime-nya begitu ia pergi, apa yang menggantikan pekerjaan server, lalu dua pelajaran soal tempat yang tepat bagi sebuah pekerjaan — termasuk kasus ketika jawabannya adalah tak memakai antarmuka sama sekali. Dua pelajaran terakhir itu sengaja ditaruh di ujung, sebab keduanya membatasi seberapa jauh pemindahan ini layak dikejar.

Metode

A. Antarmuka yang hidup di dalam runtime

Inilah jawaban atas hambatan pertama, dimulai dari rupanya sebelum pindah. Versi pertama tiap project menjalankan web server di dalam notebook lalu meminta URL-nya kepada Colab:

python
app = FastAPI()

@app.get("/", response_class=HTMLResponse)
...
url = eval_js(f"google.colab.kernel.proxyPort({PORT})")
uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="warning")

Halamannya dan tiap aksi di baliknya hidup dalam satu proses.

Ongkosnya struktural, bukan bug. Antarmuka cuma bisa ada selama runtime ada, jadi meninjau hasil berarti menyalakan sesi GPU. URL-nya baru setiap kali, jadi tak ada alamat tetap untuk kembali. Dan bagian terberat notebook-nya bukan code model melainkan antarmukanya: di project slide show, halamannya saja sudah 48 KB HTML, CSS, dan JavaScript yang tertanam di dalam string Python.

B. Ketika susunannya sendiri yang berisiko

Ini menjawab hambatan kedua, dan inilah yang memaksa perubahannya. Colab memperingatkan serta membatasi penyajian aplikasi web dari dalam runtime lewat proxy-nya. Penilaian yang tercatat saat itu gamblang: susunan itu berisiko melanggar kebijakan Google, dengan akunnya sendiri berpotensi di-banned.

Itu mengubah bingkai keputusannya. Memindahkan antarmuka keluar bukan refactor demi kerapian yang bisa ditunda. Mesin pinjaman menetapkan syarat soal apa yang boleh berjalan di atasnya, dan antarmuka yang di-hosting di dalamnya berada di sisi yang keliru dari syarat itu. Selebihnya dari artikel ini adalah pekerjaan hidup tanpa bagian yang memang harus pergi.

Penggantinya tidak dirancang dari nol. Project pengubah PDF sudah lebih dulu pindah dan sudah dipakai sungguhan, jadi project slide show dan speech meniru bentuknya dan menyalin beberapa file-nya apa adanya, termasuk client storage-nya. Susunan yang sudah terbukti adalah titik berangkat yang lebih baik ketimbang susunan baru.

C. Menjadikan runtime pekerja murni

Ini menjawab hambatan ketiga. Membuang antarmuka dari notebook slide show dikerjakan sebagai commit tersendiri yang isinya cuma penghapusan, supaya bisa ditinjau persis sebagai itu:

bash
# satu commit, isinya cuma penghapusan
endpoints removed          18
embedded page removed      48 KB
application cell           139 KB  ->  78 KB
functions                  112     ->  87
lines removed              1526

Yang bertahan adalah tiap fungsi yang mengerjakan pekerjaan sungguhan — memuat model, membuat gambar, mencatat status. Satu-satunya perubahan di lapisan itu ada pada cara ia melaporkan kegagalan. Error-nya dulu berupa HTTP exception, karena pemanggilnya endpoint HTTP. Tanpa HTTP lagi, status code tak berarti apa-apa:

python
class WorkerError(Exception):
    # error domain: antarmuka butuh pesan yang terbaca, bukan status code
    pass

raise WorkerError(...)   # mis. "project tidak ditemukan"

Empat belas raise diubah. Masing-masing menjelaskan sesuatu tentang pekerjaannya — project yang belum dibuka, item yang sedang dibuat — dan sampai ke antarmuka sebagai pesan yang terbaca pada status item itu. Worker yang tersisa tak punya web server sama sekali. Ia memuat model, menunggu, bekerja, lalu menulis.

D. Membiarkan storage yang menyajikan data

Ini menjawab hambatan keempat. Server dulu menyerahkan tiap gambar dan record kepada antarmuka. Tanpa server, antarmuka membacanya langsung dari object storage, dan broker cuma mengangkut pemberitahuan:

bash
[laptop]  interface --directly--> storage <--directly-- [Colab]  worker
              |                                             |
              +---------- broker (MQTT, triggers only) -----+

Aturan di balik gambar itu: broker tak pernah membawa byte sebuah file. Pesan menyatakan bahwa sesuatu terjadi; bendanya sendiri diambil dari storage. Itu menjaga pesan tetap kecil dan cepat, dan artinya pesan yang hilang cuma mengorbankan pemberitahuan, tak pernah data.

Membaca storage langsung dari browser punya syaratnya sendiri. Tiap gambar diambil lewat presigned URL — tautan yang memberi akses sementara ke satu objek — dan umur tautan itu dipilih dari cara antarmukanya dipakai:

typescript
// lebih panjang dari default storage (900 dtk): sesi berlangsung berjam-jam,
// dan tautan yang kedaluwarsa di tengah scroll membuat gambar gagal dimuat
presignTtl: 3600,

Akses lintas origin diperiksa langsung ketimbang diandaikan: request preflight ke storage memperlihatkan ia memantulkan origin apa pun, jadi antarmuka baru dan yang lama bisa berjalan berdampingan tanpa mengubah konfigurasi server-nya.

E. Kontrak yang tak ditegakkan siapa pun

Ini menjawab hambatan kelima. Di project speech, aturan yang wajib dimiliki bersama oleh kedua pihak ditulis sebagai tabel, sebab tak ada hal lain yang akan menyatukannya:

kontrakworker Pythonantarmuka TypeScript
nama folderslugify_name()slugifyName()
prefix topictopic_prefix()topicHash()
format durasifmt_duration()fmtDuration()
ambang selesaiDONE_MIN_BYTESDONE_MIN_BYTES
bentuk datakonstanta cell persiapantypes/project.ts
topic pesankonstanta cell persiapantypes/mqtt.ts
Aturan yang wajib dimiliki bersama kedua pihak, dan tempat tiap aturan di worker dan di antarmuka.

Alasan ia berupa tabel dan bukan komentar ada pada cara ia gagal. Kalau kedua versi fungsi nama folder berselisih, worker menulis ke satu folder dan antarmuka membaca folder lain. Kalau fungsi topic berselisih, tiap pihak mendengarkan topic yang tak pernah dipakai pihak lain. Tak satu pun menghasilkan error. Keduanya menghasilkan sunyi, yang rupanya persis sama dengan "worker-nya lambat".

Jadi tiap pasangan fungsi diuji terhadap masukan yang sama. Ketiga fungsi murni itu menghasilkan keluaran identik di 21 kasus uji sebelum antarmuka dibangun di atasnya. Tabel menyatakan apa yang harus berubah bersamaan; uji membuktikan perubahan itu memang terjadi.

F. Menulis permintaan sebelum mengirim pemberitahuan

Ini menjawab hambatan keenam. Ketika saya meminta worker speech mentranskrip sesuatu, dua hal terjadi: permintaannya dicatat, dan worker diberi tahu. Urutan itulah seluruh rancangannya:

typescript
await store.requestConvert(props.name, model.value, language.value, force)
await job
  .notifyConvert(props.name, cur.stem, model.value, language.value, force)
  .catch(() => {
    // broker tak terjangkau: permintaan sudah ada di manifest,
    // jadi worker tetap menemukannya saat memindai — cuma lebih lambat
  })
startPoll()

Permintaan masuk ke manifest project di storage lebih dulu. Baru sesudahnya pemberitahuan dikirim. Kalau pemberitahuannya hilang, tak ada yang ikut hilang: worker menemukan permintaannya saat memindai berikutnya, dan worker yang menerima pemberitahuan dijamin menemukan permintaan yang sudah lengkap menunggunya.

Antarmuka pun tak bergantung pada pemberitahuan untuk menampilkan hasil. Ia memeriksa storage tiap lima detik selama pekerjaan berlangsung. Itu diuji dari ujung ke ujung dengan broker yang tak menghasilkan satu pesan pun, dan transkripnya tetap muncul begitu ditulis. Pemberitahuan membuat segalanya lebih cepat. Storage-lah yang membuatnya benar.

G. Pekerjaan yang bukan milik sisi mana pun

Ini menjawab hambatan ketujuh. Antarmuka speech semula mengekstrak audio dari video di browser sebelum mengunggahnya, memakai build WebAssembly dari ffmpeg — program command-line yang lazim untuk mengonversi audio dan video, dikompilasi ke bentuk yang bisa dijalankan browser. Gagasannya masuk akal: unggahan lebih kecil, runtime lebih ringan.

Library itu bisa menggantung selamanya di "mengekstrak audio". Ia menyalakan worker-nya sebagai module worker, tempat pemanggilan yang dipakainya untuk memuat core-nya sendiri tidak diizinkan. Worker itu gagal selagi masih disiapkan — sebelum ia sempat memasang handler yang akan melaporkan kegagalannya — jadi tak ada error yang pernah kembali, dan promise-nya menunggu tanpa batas, sama saja di beberapa versi library itu.

Ketimbang ditambal, langkah itu dibuang, sebab ternyata ia memang tak pernah dibutuhkan. Library transkripsinya mendekode video secara langsung:

bash
# rekaman yang sama, 10 menit, 40 MB
decoded straight from the video    9600058 samples   0.6 s
decoded from the extracted audio   9600058 samples   0.6 s

Transkrip dari keduanya identik kata per kata, di tiap format container yang dicoba. Satu-satunya ongkos mengunggah video utuh adalah waktu unggah — sekitar tujuh detik lebih lama untuk file 31 MB — dan ongkos itu membeli kembali 32 MB WebAssembly, ketergantungan pada content delivery network, dan satu kelas bug yang gagal tanpa berkata apa-apa.

Memindahkan pekerjaan ke antarmuka cuma jadi perbaikan kalau antarmuka memang tempat yang baik bagi pekerjaan itu. Di sini tempat terbaiknya bukan browser dan bukan pula langkah baru di worker, melainkan langkah yang tak perlu ada.

H. Antarmuka yang tak ada

Ini menjawab hambatan kedelapan. Project speech juga punya varian headless: tanpa antarmuka, tanpa storage, tanpa broker. Ia berjalan sepenuhnya di dalam runtime Colab. Media diseret ke satu folder lewat panel file milik Colab, notebook-nya mendaftar apa yang belum ditranskrip, dan saya memilih lewat nomor:

python
print(...)   # "kosongkan lalu Enter untuk batal"
pilihan = parse_pilihan(input(...), len(antre))

if not pilihan:
    print(...)   # tak ada yang dimulai

Jawaban kosong berarti batal, bukan "semua" — menekan Enter tanpa sengaja tak boleh memulai berjam-jam transkripsi. Status tak disimpan di mana pun. Ia diturunkan dari ada tidaknya file subtitle di sebelah tiap rekaman, sama seperti worker penuh menurunkannya dari apa yang ada di storage, jadi file yang gagal sekadar ditawarkan lagi di lain waktu.

Saya memakainya dalam dua situasi: satu-dua rekaman yang dibutuhkan cepat, ketika seluruh rantai antarmuka, storage, dan broker adalah mesin yang lebih besar ketimbang yang pantas bagi pekerjaannya; dan ketika antarmuka atau storage sedang tak bisa dipakai, sebagai cara tetap bekerja.

Keberadaannya menyatakan sesuatu tentang rancangan selebihnya. Memindahkan antarmuka keluar adalah jawaban yang tepat untuk satu kelompok kebutuhan — pekerjaan panjang, banyak item, peninjauan dari waktu ke waktu. Jawaban itu bukan satu-satunya, dan untuk pekerjaan singkat dengan satu file, antarmuka yang tak ada tak mungkin gagal.

Hasil dan Pembahasan

A. Apa yang dijumlahkan kedelapan keputusan itu

Project slide show dan speech kini berjalan dengan antarmuka di laptop dan worker di Colab yang tak punya web server. Antarmukanya tetap terbuka sementara runtime datang dan pergi, bisa dipakai meninjau hasil tanpa sesi GPU, dan tak lagi bergantung pada apa pun yang mungkin dibatasi Colab. Project pengubah PDF pindah lebih dulu.

KeputusanYang dibeliAlternatif yang ditolak
antarmuka di laptopselamat dari runtime; alamat tetapweb server di dalam Colab, lewat proxy-nya
tiru susunan yang sudah terbuktiperilaku yang dikenal sejak hari pertamamerancang pemisahan dari nol
penghapusan dalam commit tersendiripenghapusan bisa ditinjau sebagai penghapusanmenghapus dan menyambung ulang dalam satu perubahan
error domain ketimbang error HTTPpesan terbaca tanpa status code palsumempertahankan HTTP exception tanpa HTTP
storage menyajikan data, broker cuma pemicupesan hilang tak pernah menghilangkan datamengirim file lewat broker
tabel kontrak plus uji berpasanganpenyimpangan tertangkap sebelum jadi sunyimemercayai dua codebase tetap selaras
permintaan dicatat sebelum pemberitahuanperintah selamat dari pesan yang hilangpermintaan cuma dibawa pesannya
video didekode di workertranskrip identik, 32 MB code lebih sedikitmengekstrak audio di browser
varian headless untuk pekerjaan singkattanpa mesin bila memang tak perluselalu memakai rantai penuh
Delapan keputusan, apa yang dibeli masing-masing, dan alternatif yang digantikannya.

B. Kesepakatanlah server-nya sekarang

Web server mengerjakan tugas senyap yang gampang terlewat sampai ia hilang: ia satu-satunya tempat yang dipercaya kedua pihak. Singkirkan dia, dan tugas itu tak ikut lenyap. Ia harus diambil alih sesuatu, dan di rancangan ini ia diambil alih kesepakatan — soal nama folder, topic, urutan tulis, dan salinan kebenaran mana yang menang.

Sebagian besar hambatan adalah proses pengalihan itu. Storage mengambil alih memegang kebenaran, dan karena itulah permintaan ditulis ke sana sebelum ada yang diberi tahu. Broker cuma mengambil alih bagian pekerjaan server yang menyangkut waktu, dan karena itulah ia membawa pemicu dan tak pernah data. Tabel kontrak dan uji-ujinya mengambil alih bagian yang menyangkut makna, sebab tanpa server tak ada hal lain yang akan menyadari dua codebase saling menjauh.

Pelajaran umumnya berlaku jauh melampaui Colab. Ketika satu sistem kehilangan bagian tengahnya, apa pun yang dulu dijamin bagian tengah itu harus dialihkan dengan sengaja — atau ia tak lagi terjamin, dan tanda pertamanya biasanya sunyi.

C. Yang tidak dicakup

Antarmuka yang membaca dan menulis storage secara langsung adalah rancangan untuk tool internal yang dipakai satu orang di mesinnya sendiri, dan hanya di situ. Antarmuka yang dibuka bagi orang lain kembali lewat server — atau paling tidak lewat layanan yang membagikan akses berumur pendek dan berlingkup sempit.

Simpulan

GPU 4 GB menaruh pekerjaan berat beberapa project di runtime Colab pinjaman, dan langkah pertama yang alami adalah menaruh antarmukanya di sana juga. Itu bekerja, tapi mengikat halaman pada umur sesi dan bersandar pada penyajian aplikasi web lewat proxy Colab — susunan yang diperingatkan dan dibatasi oleh Colab.

Memindahkan antarmuka keluar menjadikan runtime pekerja murni, dilucuti dari delapan belas endpoint dan satu halaman 48 KB, melaporkan error domain ketimbang error HTTP. Storage mengambil alih penyajian data, dengan broker dibatasi pada pemicu. Tabel kontrak dan uji berpasangan mengambil alih apa yang dulu dijamin server soal makna. Permintaan dicatat sebelum ada yang diberi tahu, jadi pesan yang hilang cuma mengorbankan waktu, bukan pekerjaan. Pekerjaan yang bukan milik sisi mana pun dibuang, bukan ditambal. Dan untuk pekerjaan singkat, antarmuka yang tepat ternyata tak ada sama sekali.

Yang terbawa ke mana-mana adalah pertanyaan ini, tiap kali bagian tengah satu sistem disingkirkan: apa yang selama ini dijaminnya diam-diam — lalu beri tiap jaminan itu rumah baru dengan sengaja.

Istilah

proxy port
Fitur Colab yang membuka port di dalam runtime sebagai URL di browser, dan begitulah web server yang berjalan di notebook bisa dijangkau.
pekerja murni
Proses yang cuma mengerjakan pekerjaannya — memuat, menunggu, menghitung, menulis — tanpa web server maupun antarmuka sendiri.
presigned URL
Tautan ke satu objek di storage, ditandatangani untuk memberi akses sementara tanpa membagikan kredensial storage kepada siapa pun yang memakai tautannya.
manifest
Record di storage yang menjelaskan sebuah project dan item-itemnya, termasuk permintaan yang menunggu diproses. Di sini ia sumber kebenaran untuk apa yang sudah diminta.
headless
Berjalan tanpa antarmuka apa pun. Pekerjaan dimulai dan dipilih dari notebook itu sendiri, dan hasilnya dibaca langsung dari tempat ia ditulis.
module worker
Worker browser yang dinyalakan sebagai modul JavaScript. Beberapa cara lama memuat code, seperti importScripts(), tak diizinkan di dalamnya.

Technologies covered

  • Python
  • TypeScript
  • FastAPI
  • Vue
  • FFmpeg
  • Google Colab
  • MinIO
  • MQTT

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Related Articles

Three camera frames of a face, facing forward, turned right, and turned left, each checked off, flowing into locked private storage that a reviewer looks into

Service Python yang meminta orang di depan kamera menghadap depan, menoleh ke kanan, lalu ke kiri, menangkap satu frame tajam per pose, dan menyimpan ketiganya sebagai bukti privat untuk peninjau manusia — membuktikan orang hidup hadir, bukan siapa dia.

10 min read Read more →
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

Service Python yang menandatangani PDF sampai PAdES B-LTA dengan stempel yang terlihat — menanamkan stempel waktu, sertifikat, dan bukti pencabutan, lalu stempel waktu arsip — supaya tanda tangan tetap bisa diverifikasi lama setelah sertifikatnya kedaluwarsa, dan memverifikasi PDF bertanda tangan.

10 min read Read more →
One endpoint procedure as a column of ordered steps, a request entering at the top and the answer leaving at the bottom, each step calling its own concern module beside it

Tiap endpoint adalah satu prosedur yang membawa request dari awal sampai selesai, dengan urusannya di module di sampingnya — Transaction Script Fowler dalam bentuk yang saya berikan pada service Python kecil bertugas tunggal, dibundel jadi satu binary, sampai aturannya tumbuh.

8 min read Read more →