<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://adipurnm.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://adipurnm.com/" rel="alternate" type="text/html" /><updated>2026-04-23T19:03:48+00:00</updated><id>https://adipurnm.com/feed.xml</id><title type="html">adipurnm’s log</title><subtitle>kebun digital</subtitle><author><name>Adi Purnama</name></author><entry><title type="html">Post dari PicoClaw</title><link href="https://adipurnm.com/post-dari-picoclaw" rel="alternate" type="text/html" title="Post dari PicoClaw" /><published>2026-04-23T05:48:00+00:00</published><updated>2026-04-23T05:48:00+00:00</updated><id>https://adipurnm.com/post-dari-picoclaw</id><content type="html" xml:base="https://adipurnm.com/post-dari-picoclaw"><![CDATA[<p>Post ini dibuat oleh PicoClaw.</p>]]></content><author><name>Adi Purnama</name></author><summary type="html"><![CDATA[Post ini dibuat oleh PicoClaw.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Domain sesuai tujuan</title><link href="https://adipurnm.com/domain-sesuai-tujuan" rel="alternate" type="text/html" title="Domain sesuai tujuan" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://adipurnm.com/domain-sesuai-tujuan</id><content type="html" xml:base="https://adipurnm.com/domain-sesuai-tujuan"><![CDATA[<p>Baru-baru ini saya mengembalikan domain ini menjadi domain .my.id kembali, setelah sebelumnya pakai domain .com.</p>

<p>Ingat dengan <a href="https://adipurnm.my.id/domain-my-id-jelek-di-mata-google">tulisan ini</a>? Ya, awalnya saya beli domain adipurnm.com semata-mata hanya untuk menguji asumsi saya terhadap SEO di Google. Dan yah, sisanya bisa di baca saja di tulisan tersebut.</p>

<p>Nah, karena uji coba sudah selesai, saya pikir alangkah baiknya kalau dua domain ini saya gunakan sebagaimana mestinya. Domain .com/commercial saya gunakan untuk kepentingan komersil, seperti jualan produk digital. Domain .my.id saya gunakan untuk menulis hal-hal yang bersifat personal.</p>

<p>Sempat sedikit dilema, tapi pada akhirnya saya memantapkan niatan saya ini.</p>

<h2 id="domain-com-saya-pakai-wordpress-tapi-akhirnya-saya-bikin-ulang-pakai-rails">Domain .com saya pakai WordPress, tapi akhirnya saya bikin ulang pakai Rails.</h2>

<p>WordPress sebenarnya bagus-bagus aja, tapi seperti yang saya tulis di tulisan <a href="/harga-sebuah-kebebasan">Harga Sebuah Kebebasan</a>, saya akhirnya beralih ke Rails supaya fiturnya bisa saya custom sesuka saya.</p>

<p>Jika misal, suatu waktu saya ingin menambahkan fitur artikel premium, saya tinggal minta AI buat coding aja. Di WordPress, sepertinya perlu plugin tambahan, dan berbayar. Unless, kita bikin sendiri plugin-nya, yang mana ujung-ujungnya sama-sama ngoding sih.</p>

<p>Yang paling utama itu sebenarnya dari sisi scaling dan performa. Saya lebih familiar scale aplikasi Rails ketimbang WordPress. Dari sisi performa juga Rails terasa lebih cepat berkat bantuan Turbo. WordPress pun bisa-bisa aja sebenarnya pakai page/object cache. Cuma ya harus install plugin lagi.</p>

<p>Alasan lain adalah, semakin banyak plugin yang diinstall, semakin banyak menu-menu di halaman admin. Saya merasa bloated aja. Tiap buka WP Admin, menunya bejibun. Padahal saya cuma perlu beberapa menu aja.</p>

<p>Banyak plugin juga berarti menbuat website lebih rentan untuk diretas. Apalagi plugin-plugin yang sudah lama tidak pernah diupdate karena ditinggal oleh maintainernya, ini bisa jadi celah keamanan.</p>

<p>Sedangkan kalau bikin sendiri pakai Rails, saya tau semua kode yang saya buat. Dependensi eksternalnya paling ke gem untuk halaman admin. Sisanya dependensi bawaan Rails semua. Rasanya lebih tenang aja. Kalau ada apa-apa, saya bisa langsung tau bagaimana cara memperbaikinya.</p>]]></content><author><name>Adi Purnama</name></author><category term="Blogging" /><summary type="html"><![CDATA[Baru-baru ini saya mengembalikan domain ini menjadi domain .my.id kembali, setelah sebelumnya pakai domain .com.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Tentang Publish E-book di Google Play Book</title><link href="https://adipurnm.com/tentang-publish-e-book-di-google-play-book" rel="alternate" type="text/html" title="Tentang Publish E-book di Google Play Book" /><published>2026-03-11T00:00:00+00:00</published><updated>2026-03-11T00:00:00+00:00</updated><id>https://adipurnm.com/tentang-publish-e-book-di-google-play-book</id><content type="html" xml:base="https://adipurnm.com/tentang-publish-e-book-di-google-play-book"><![CDATA[<p>Beberapa waktu lalu, mas Hanif Mahaldi di Threads sharing terkait <a href="https://www.threads.com/@hanif.mahaldi/post/DVYmRYPkb69?xmt=AQF0VHOuJbHRkEFLpui-QXeFbqz-4jJkSevBo5PyQQf7YA" target="_blank">tips mempublikasikan e-book di Google Play Book</a> secara cuma-cuma. Sebelumnya saya sudah mencoba sendiri, namun cukup bingung mengisinya karena ada beberapa istilah yang tidak saya pahami. Ini membuat saya kebingungan untuk mengambil keputusan: takut salah.</p>

<p>Alhasil, akun Google Play Book saya di-ban oleh Google. Saya jadi malas untuk melanjutkan.</p>

<p>Tapi, rasa penasaran itu masih ada. Ditambah, kalau memang belum tembus, mas Hanif menyarankan untuk pakai email lain.</p>

<p>Sebenarnya kalau di-ban begini kita bisa mengajukan banding. Tapi, sepertinya akan cukup ribet kalau Google meminta dokumen-dokumen pendukung. Jadi, buat akun baru saya rasa akan lebih sederhana dan masuk akal.</p>

<p>Setelah dicoba, rupanya benar. Hanya dalam waktu kurang dari 24 jam, akun saya sudah disetujui oleh Google dan sudah bisa upload e-book.</p>

<p>Kini, keempat e-book saya sudah tersedia di Google. Saya pasang harga diskon hingga akhir Mei nanti. Silakan diunduh. Cari “Adi Purnama” di Google Play Book, atau bisa klik tautan berikut ini:</p>

<ul>
  <li><a href="https://play.google.com/store/books/details?id=gqvFEQAAQBAJ" target="_blank">Melek Digital: Bagaimana tetap Aman di Era yang Semakin Tidak Aman</a></li>
  <li><a href="https://play.google.com/store/books/details?id=kAvGEQAAQBAJ" target="_blank">Rahasia Mulai Bisnis Pembuatan Website Tanpa Skill Desain</a></li>
  <li><a href="https://play.google.com/store/books/details?id=9AvGEQAAQBAJ" target="_blank">Self-hosting dengan Dokku</a></li>
  <li><a href="https://play.google.com/store/books/details?id=5ArGEQAAQBAJ" target="_blank">The Programmer’s Mindset: Rahasia yang Tidak Diajarkan di Bootcamp</a></li>
</ul>]]></content><author><name>Adi Purnama</name></author><category term="SideHustle" /><summary type="html"><![CDATA[Beberapa waktu lalu, mas Hanif Mahaldi di Threads sharing terkait tips mempublikasikan e-book di Google Play Book secara cuma-cuma. Sebelumnya saya sudah mencoba sendiri, namun cukup bingung mengisinya karena ada beberapa istilah yang tidak saya pahami. Ini membuat saya kebingungan untuk mengambil keputusan: takut salah.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Install Umami SQLite di VPS menggunakan Dokku</title><link href="https://adipurnm.com/install-umami-sqlite-di-vps-menggunakan-dokku" rel="alternate" type="text/html" title="Install Umami SQLite di VPS menggunakan Dokku" /><published>2026-03-09T00:00:00+00:00</published><updated>2026-03-09T00:00:00+00:00</updated><id>https://adipurnm.com/install-umami-sqlite-di-vps-menggunakan-dokku</id><content type="html" xml:base="https://adipurnm.com/install-umami-sqlite-di-vps-menggunakan-dokku"><![CDATA[<p>Sebagai software analytic open source, Umami punya fitur analytic yang cukup lengkap, namun tidak terkesan bloated. Fitur inti yang diperlukan untuk web analytic sudah tersedia di Umami.</p>

<p>Umami itu hampir sempurna. Satu-satunya hal yang memberatkan saya adalah database-nya menggunakan PostgreSQL.</p>

<p>Ini berarti, kalau mau install Umami di VPS, harus nambah satu service PostgreSQL baru.</p>

<p>Bukan saya anti dengan PostgreSQL. Hanya saja:</p>

<ol>
  <li>VPS saya ini resource-nya sudah sangat pas-pasan. Nambah service PostgeSQL baru berarti nambah penggunaan resource server.</li>
  <li>Menggunakan PostgreSQL untuk traffic kecil terlalu overkill. SQLite saja harusnya sudah cukup.</li>
</ol>

<p>Oleh karena itu, saya mencari Umami dengan support database SQLite.</p>

<p>Indahnya open source itu, ada saja orang yang berkenan menambahkan support SQLite untuk Umami: <a href="https://github.com/Maxime-J/umami-sqlite">https://github.com/Maxime-J/umami-sqlite</a>. Sudah tersedia juga Docker image yang siap digunakan di server.</p>

<p>Dengan begini, saya pun bisa deploy Umami di server menggunakan Dokku.</p>

<h2 id="bagaimana-caranya">Bagaimana caranya?</h2>

<ol>
  <li>Pastikan Dokku sudah terinstall di VPS kamu. Kalau belum, silahkan install dulu di VPS dengan mengikut <a href="https://dokku.com/docs/getting-started/installation" class="underline--dotted">dokumentasi ini</a>.</li>
  <li>Buat app baru pakai perintah <code class="language-plaintext highlighter-rouge">dokku apps:create umami</code>.</li>
  <li>Buat persistent storage dan mount ke container Umami. Bisa pakai docker volume supaya lebih sederhana. Cara buatnya bisa pakai <code class="language-plaintext highlighter-rouge">docker volume create umami</code>.</li>
  <li>Mount volume <code class="language-plaintext highlighter-rouge">umami</code> ke <code class="language-plaintext highlighter-rouge">/db</code> di container menggunakan perintah <code class="language-plaintext highlighter-rouge">dokku storage:mount umami umami:/db</code>.</li>
  <li>Di laptop komputer kamu, buatlah sebuah random string yang akan digunakan sebagai app secret menggunakan perintah <code class="language-plaintext highlighter-rouge">openssl rand -hex 32</code> atau perintah sejenis.</li>
  <li>Set env var yang diperlukan: <code class="language-plaintext highlighter-rouge">dokku config:set umami DATABASE_URL=file:/db/umami.db APP_SECRET=secret_token</code>. Ganti <code class="language-plaintext highlighter-rouge">secret_token</code> dengan output dari poin 5.</li>
  <li>Tambah domain/subdomain: <code class="language-plaintext highlighter-rouge">dokku domains:add dokku stats.example.com</code>. Ganti <code class="language-plaintext highlighter-rouge">stats.example.com</code> dengan domain kamu.</li>
  <li>Nyalakan Let’s Encrypt, atau install custom certificate yang didapat dari Cloudflare.</li>
  <li>Atur port forwarding: <code class="language-plaintext highlighter-rouge">dokku ports:set umami http:80:3000 https:443:3000</code>.</li>
  <li>Install Umami dengan perintah <code class="language-plaintext highlighter-rouge">dokku git:from-image umami ghcr.io/maxime-j/umami-sqlite:latest</code>.</li>
  <li>Kunjungi domain Umami kamu dan pastikan proses instalasi berhasil. Jika berhasil, kamu akan diarahkan ke halaman login Umami. Masuklah dengan akun default seperti yang sudah dijelaskan <a href="https://shop.adipurnm.com/products/self-hosting-dengan-dokku" class="underline--dotted">di sini</a>.</li>
  <li>Selesai!</li>
</ol>

<p>Dengan begini, saya masih tetap bisa pakai Umami tanpa harus install PostgreSQL. Sejauh ini tidak ada kendala.</p>

<p>Kalau kamu punya project yang ingin di-deploy menggunakan Dokku juga, saya nulis e-book gratis berjudul <a href="https://shop.adipurnm.com/products/self-hosting-dengan-dokku">Self-hosting dengan Dokku</a>.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><summary type="html"><![CDATA[Sebagai software analytic open source, Umami punya fitur analytic yang cukup lengkap, namun tidak terkesan bloated. Fitur inti yang diperlukan untuk web analytic sudah tersedia di Umami.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/2026/2026-03-10_00-44.webp" /><media:content medium="image" url="https://adipurnm.com/assets/images/2026/2026-03-10_00-44.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">MicroSaaS-mu belum butuh payment gateway</title><link href="https://adipurnm.com/microsaas-baru-belum-butuh-payment-gateway" rel="alternate" type="text/html" title="MicroSaaS-mu belum butuh payment gateway" /><published>2026-03-05T05:05:00+00:00</published><updated>2026-03-05T05:05:00+00:00</updated><id>https://adipurnm.com/microsaas-baru-belum-butuh-payment-gateway</id><content type="html" xml:base="https://adipurnm.com/microsaas-baru-belum-butuh-payment-gateway"><![CDATA[<p>Dear solopreneurs,</p>

<p>Kalau kamu lagi buat micro SaaS baru, saya sarankan jangan pakai payment gateway dulu kalau jumlah transaksinya masih hitungan jari.</p>

<p>Kenapa?</p>

<ol>
  <li>Overkill. Pesanan masih bisa kamu atur secara manual.</li>
  <li>Memaksimalkan keuntungan. Biaya layanan payment gateway itu per transaksinya lumayan, tergantung metode pembayaran yang dipilih. Atur manual = uang langsung masuk ke rekening kamu tanpa potingan apapun lagi.</li>
  <li>Administrasi akun payment gateway itu ribet. Beberapa layanan prosesnya bisa memakan waktu cukup lama dan bahkan harus punya badan usaha.</li>
  <li>MicroSaaS-mu belum tentu product market fit. Ketimbang ngembangin integrasi payment gateway duluan, lebih baik waktu kamu pakai untuk minta feedback sebanyak-banyaknya ke user.</li>
</ol>

<p>Mintalah user untuk transfer ke nomor rekeningmu langsung. Atau, kamu juga bisa buat QRIS untuk menerima pembayaran.</p>

<p>Proses pembuatan QRIS lumayan cepat. Saya buat di GoPay Merchant, sehari langsung jadi dan bisa digunakan. Nggak ada potongan MDR-nya juga malah.</p>

<p>Integrasikan proses pembayaran dengan bot Telegram. Bot Telegram itu:</p>

<ul>
  <li>gratis</li>
  <li>integrasinya mudah, apalagi dibantu AI</li>
  <li>menyediakan berbagai jenis inline keyboard sesuai kebutuhan kamu</li>
</ul>

<p>Alurnya: setiap user mengunggah bukti pembayaran, kirim notifikasi bukti pembayaran ke bot Telegram. Notifikasi harus mengandung:</p>

<ul>
  <li>informasi pembeli</li>
  <li>informasi pesanan</li>
  <li>bukti pembayaran yang diunggah pembeli</li>
  <li>tombol Setujui/Tolak pesanan</li>
</ul>

<p>Dengan begini, proses mengatur pesanan jadi lebih mudah. Kamu nggak perlu masuk ke halaman admin hanya untuk menyetujui/menolak pesanan.</p>

<p>Dan inilah yang saya terapkan di Kanyaah dan di toko online saya sekarang. Sejauh ini cukup happy dengan workflow ini.</p>]]></content><author><name>Adi Purnama</name></author><category term="SideHustle" /><summary type="html"><![CDATA[Dear solopreneurs,]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Saya menulis e-book baru lagi berjudul Melek Digital</title><link href="https://adipurnm.com/saya-menulis-e-book-baru-lagi-berjudul-melek-digital" rel="alternate" type="text/html" title="Saya menulis e-book baru lagi berjudul Melek Digital" /><published>2026-03-05T00:00:00+00:00</published><updated>2026-03-05T00:00:00+00:00</updated><id>https://adipurnm.com/saya-menulis-e-book-baru-lagi-berjudul-melek-digital</id><content type="html" xml:base="https://adipurnm.com/saya-menulis-e-book-baru-lagi-berjudul-melek-digital"><![CDATA[<p>Melek Digital pada mulanya berupa sebuah seri di blog ini yang dapat diakses <a href="/melek-digital">di halaman ini</a>. Tapi, pada akhirnya saya tulis ulang ke dalam sebuah e-book berjudul Melek Digital: Bagaimana tetap Aman di Era yang Semakin Tidak Aman, dengan banyak penambahan isi.</p>

<p>E-book saya buat atas keresahan saya terhadap modus-modus penipuan yang kian marak dan semakin tertarget. Beberapa orang di lingkungan terdekat saya sudah menjadi korban, termasuk orang tua saya sendiri.</p>

<p>Keresahan lainnya muncul karena minimnya literasi digital di sebagian kelompok masyarakat, terutama generasi orang tua kita. Ada yang memperlakukan data pribadi yang sensitif layaknya data publik. Misalnya, memberi tahu kode PIN m-banking kepada pihak lain yang bahkan bukan orang terdekat.</p>

<p>Saya tidak mengada-ngada. Ini benar-benar terjadi di kehidupan nyata.</p>

<p>Saking mengerikannya modus-modus penipuan sekarang ini, sampai-sampai orang yang istilahnya terpelajar dan sudah cukup melek digital pun bisa saja tertipu kalau tidak teliti. Apalagi, di era di mana hampir semua yang berbau digital dapat dimanipulasi dengan mudah dengan bantuan AI. Sebagai orang yang hidup di zaman serba digital, kesadaran akan kemanan digital sudah seharusnya dimiliki oleh semua orang.</p>

<p>E-book ini menjadi salah satu upaya saya untuk menumbuhkan kesadaran akan keamanan digital di masyarakat. Berisi tentang apa saja yang termasuk ke dalam kategori data pribadi yang sensitif, bagaimana seharusnya kita memperlakukannya, dan modus-modus penipuan apa saja yang kerap terjadi serta bagaimana cara mengantisipasinya.</p>

<p>Mengacu ke tujuan utama e-book ini ditulis, saya rasa format e-book lebih relevan. Kesan buku panduannya lebih terasa ketimbang menggunakan website. Selain itu, layaknya buku fisik, e-book juga bisa dibaca secara offline. Masyarakat yang masih awam dengan web browser pun bisa mengakses e-book ini di perangkat masing-masing. Cukup buka dan tinggal scroll saja sampai akhir halaman.</p>

<p>Materi di dalam e-book akan saya perbarui jika ada modus penipuan terbaru yang belum dicantumkan. Jadi, silakan diunduh dan disebarkan ke keluarga dan kerabat terdekat. Saling lindungi satu sama lain supaya terhindar dari kejahatan siber.</p>

<p>E-book ini dapat diunduh secara gratis di toko online saya. Dapatkan e-booknya <a href="https://shop.adipurnm.com/products/melek-digital" target="_blank">di sini</a>.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><summary type="html"><![CDATA[Melek Digital pada mulanya berupa sebuah seri di blog ini yang dapat diakses di halaman ini. Tapi, pada akhirnya saya tulis ulang ke dalam sebuah e-book berjudul Melek Digital: Bagaimana tetap Aman di Era yang Semakin Tidak Aman, dengan banyak penambahan isi.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Kamu sebenarnya punya tools productivity gratis bernama todo.txt</title><link href="https://adipurnm.com/kamu-sebenarnya-punya-tools-productivity-gratis-bernama-todo-txt" rel="alternate" type="text/html" title="Kamu sebenarnya punya tools productivity gratis bernama todo.txt" /><published>2026-02-20T00:00:00+00:00</published><updated>2026-02-20T00:00:00+00:00</updated><id>https://adipurnm.com/kamu-sebenarnya-punya-tools-productivity-gratis-bernama-todo-txt</id><content type="html" xml:base="https://adipurnm.com/kamu-sebenarnya-punya-tools-productivity-gratis-bernama-todo-txt"><![CDATA[<p>Beberapa orang rela membayar biaya langganan hanya untuk aplikasi sederhana seperti to-do list. Padahal, aplikasi to-do list intinya ya begitu-begitu aja. Tambah todo, kasih beberapa atribute seperti label, dsb. Lalu atur prioritas mana yang harus diselesaikan terlebih dahulu atau masih bisa dilakukan di lain waktu. Setelah selesai, tandai to-do tersebut sebagai selesai.</p>

<p>Memang, ada beberapa aspek tertentu yang menyebabkan orang-orang rela membayar, seperti fitur yang orang-orang tersebut perlukan hanya ada di layanan tersebut. atau mungkin hanya sebatas alasan estetika aplikasi.</p>

<h2 id="mengenal-format-todotxt">Mengenal format todo.txt</h2>

<p>Mencatat dan mengatur prioritas pekerjaan itu pada dasarnya bisa dilakukan di dalam sebuah file plain text biasa bernama <code class="language-plaintext highlighter-rouge">todo.txt</code>. Tanpa ketergantungan terhadap layanan pihak ketiga. Data 100% milik kamu.</p>

<p>Kalau kamu hanya memerlukan fitur dasar dan tidak terlalu masalah dengan tampilan yang sederhana, mekanisme ini sangat cocok buat kamu.</p>

<p>File <code class="language-plaintext highlighter-rouge">todo.txt</code> ini juga punya format khusus lho. Format ini nantinya akan sangat mempermudah untuk mengatur urutan atau prioritas todo.</p>

<p>Todo yang masih ongoing biasanya formatnya begini:</p>

<pre><code class="language-todotxt">priority created-at todo-name @konteks +project due:due-date
</code></pre>

<p>Todo yang sudah selesai biasanya formatnya begini:</p>

<pre><code class="language-todotxt">x created-at done-at todo-name @konteks +project due:due-date pri:priority
</code></pre>

<p>Mari saya jelaskan sedikit:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">priority/pri</code>: skala prioritas todo, dimulai dari <code class="language-plaintext highlighter-rouge">(A)</code> (paling urgent) sampai <code class="language-plaintext highlighter-rouge">(Z)</code> (sangat tidak urgent)</li>
  <li><code class="language-plaintext highlighter-rouge">created-at</code>: tanggal todo dibuat</li>
  <li><code class="language-plaintext highlighter-rouge">todo-name</code>: nama todo</li>
  <li><code class="language-plaintext highlighter-rouge">@konteks</code>: situasi, lokasi, alat, atau orang yang harus ada untuk menyelesaikan todo</li>
  <li><code class="language-plaintext highlighter-rouge">+project</code>: mengelompokkan todo ke dalam sebuah target atau project, membantu untuk melacak progress terhadap outcome spesifik</li>
  <li><code class="language-plaintext highlighter-rouge">due</code>: tanggal tenggat waktu todo harus selesai dilaksanakan</li>
</ul>

<p>Sebagai contoh: hari senin saya harus service motor di dealer. Jika diubah ke dalam format <code class="language-plaintext highlighter-rouge">todo.txt</code>, kurang lebih akan jadi begini:</p>

<pre><code class="language-todotxt">(A) 2026-02-18 service motor @dealer +maintenance due:2026-02-19
</code></pre>

<p>Pada dasarnya, sebenarnya kita cukup ketik “Service motor di dealer” di file <code class="language-plaintext highlighter-rouge">todo.txt</code>, dan sah-sah saja. Tapi, dengan memakai format di atas, meskipun memang lebih ribet, todo jadi bisa dapat diurutkan atau difilter berdasarkan atribut-atribut tertentu. Atribut-atribut tersebut yaitu prioritas, project, konteks, tanggal dibuat, tenggat waktu, atau deskripsi.</p>

<p>Ini sangat bermanfaat untuk menentukan todo mana saja yang harus dikerjakan berdasarkan skala prioritas saat itu juga. Misal, ternyata project freelance lebih prioritas, maka kita bisa urutkan atau filter todo berdasarkan project freelance.</p>

<h2 id="todosh-antarmuka-cli-untuk-todotxt"><code class="language-plaintext highlighter-rouge">todo.sh</code>: antarmuka CLI untuk <code class="language-plaintext highlighter-rouge">todo.txt</code></h2>

<p>Kalau kamu pengguna Linux atau Mac, <code class="language-plaintext highlighter-rouge">todo.sh</code> bisa kamu gunakan untuk mengatur file <code class="language-plaintext highlighter-rouge">todo.txt</code> kamu. Unduh versi terbaru <a href="https://github.com/todotxt/todo.txt-cli/releases">di sini</a>. Pilih file yang ekstensi-nya <code class="language-plaintext highlighter-rouge">.zip</code> atau <code class="language-plaintext highlighter-rouge">.tar.gz</code>.</p>

<p>Ada 3 file penting di dalam file <code class="language-plaintext highlighter-rouge">.zip</code> atau <code class="language-plaintext highlighter-rouge">.tar.gz</code> todo txt CLI.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">todo.cfg</code>: file config yang akan dibaca oleh <code class="language-plaintext highlighter-rouge">todo.sh</code></li>
  <li><code class="language-plaintext highlighter-rouge">todo.sh</code>: antarmuka CLI</li>
  <li><code class="language-plaintext highlighter-rouge">todo_completion</code>: autocomplete perintah-perintah yang tersedia di <code class="language-plaintext highlighter-rouge">todo.sh</code></li>
</ul>

<p>Supaya bisa digunakan di terminal, ketiga file tersebut harus disimpan di folder yang semestinya:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">todo.cfg</code> disimpan di <code class="language-plaintext highlighter-rouge">~/.config/todo</code> dengan nama <code class="language-plaintext highlighter-rouge">config</code></li>
  <li><code class="language-plaintext highlighter-rouge">todo.sh</code> disimpan di <code class="language-plaintext highlighter-rouge">~/.local/bin</code></li>
  <li><code class="language-plaintext highlighter-rouge">todo_completion</code> disimpan di <code class="language-plaintext highlighter-rouge">~/.local/share/bash-completion/completions</code></li>
</ul>

<p>Supaya nggak repot ketik <code class="language-plaintext highlighter-rouge">todo.sh</code> setiap kali mau melakukan manipulasi terhadap todo, tambahkan <code class="language-plaintext highlighter-rouge">alias t="todo.sh"</code> di file <code class="language-plaintext highlighter-rouge">~/.bashrc</code> atau di file RC shell favorit kamu dan jalankan <code class="language-plaintext highlighter-rouge">source ~/.bashrc</code> di terminal untuk memuat ulang konfigurasi shell. Ketik <code class="language-plaintext highlighter-rouge">t</code> di terminal, kalau menampilkan usage, berarti <code class="language-plaintext highlighter-rouge">todo.sh</code> sudah berhasil diinstall.</p>

<p>File todo secara default akan disimpan di <code class="language-plaintext highlighter-rouge">HOME</code>. Kalau kamu ingin menempatkannya di folder lain, kamu bisa mengubahnya di <code class="language-plaintext highlighter-rouge">~/.config/todo/config</code>. Kamu juga bisa uncomment baris-baris yang berkaitan dengan text color supaya list todo di terminal jadi berwarna.</p>

<h2 id="perintah-perintah-dasar-todosh">Perintah-perintah dasar <code class="language-plaintext highlighter-rouge">todo.sh</code></h2>

<p>Berikut beberapa perintah dasar yang menurut saya sangat sering dipakai:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">t add "New todo"</code>: menambahkan todo baru</li>
  <li><code class="language-plaintext highlighter-rouge">t replace ID "Updated todo"</code>: untuk mengubah todo yang ada</li>
  <li><code class="language-plaintext highlighter-rouge">t append ID "appended text"</code>: menambahkan text ke todo yang sudah ada, cocok kalau kita lupa set context/project/hal-hal lainnya</li>
  <li><code class="language-plaintext highlighter-rouge">t pri ID NEW_PRI</code>: menambahkan atau mengubah prioritas todo, misal dari A ke B</li>
  <li><code class="language-plaintext highlighter-rouge">t depri ID</code>: menghapus prioritas todo</li>
  <li><code class="language-plaintext highlighter-rouge">t do ID</code>: menandai todo sebagai done dan memindahkannya ke file <code class="language-plaintext highlighter-rouge">done.txt</code></li>
  <li><code class="language-plaintext highlighter-rouge">t del ID</code>: menghapus todo</li>
  <li><code class="language-plaintext highlighter-rouge">t ls</code>: menampilkan list pada file <code class="language-plaintext highlighter-rouge">todo.txt</code></li>
  <li><code class="language-plaintext highlighter-rouge">t lsp</code>: menampilkan list todo yang prioritas pada file <code class="language-plaintext highlighter-rouge">todo.txt</code></li>
</ul>

<p>Daftar lengkapnya bisa dicek menggunakan perintah <code class="language-plaintext highlighter-rouge">t help</code>.</p>

<h2 id="menampilkan-todo-sesuai-prioritas-pekerjaan">Menampilkan todo sesuai prioritas pekerjaan</h2>

<p>Tentunya tidak selalu prioritas A sampai Z itu mutlak. Kadang, kita ingin fokus mengerjakan pekerjaan kantor terlebih dahulu. Atau, kita ingin mengerjakan pekerjaan sampingan karena kebetulan sedang memiliki waktu senggang.</p>

<p>Misal, kita punya todo sebagai berikut:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(A) Perbaiki bug fitur 1 @work
(A) Perbaiki bug fitur 2 @work
(B) Buat implementation plan untuk fitur baru @work
Review kode yang sudah bisa di-review @work
(A) Tambah fitur subscription @freelance
Perbaiki bug fitur 1 @freelance
</code></pre></div></div>

<p>Jika kita ingin menampilkan todo yang berkaitan dengan pekerjaan kantor, gunakan perintah <code class="language-plaintext highlighter-rouge">t ls work</code>. Hasilnya sebagai berikut:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(A) Perbaiki bug fitur 1 @work
(A) Perbaiki bug fitur 2 @work
(B) Buat implementation plan untuk fitur baru @work
Review kode yang sudah bisa di-review @work
</code></pre></div></div>

<p>Jika ingin menampilkan todo yang prioritas saja, gunakan perintah <code class="language-plaintext highlighter-rouge">t lsp work</code>. Hasilnya sebagai berikut:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(A) Perbaiki bug fitur 1 @work
(A) Perbaiki bug fitur 2 @work
(B) Buat implementation plan untuk fitur baru @work
</code></pre></div></div>

<p>Jika ingin menampilkan pekerjaan prioritas di kantor, gunakan perintah <code class="language-plaintext highlighter-rouge">t lsp a work</code>. Hasilnya sebagai berikut:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(A) Perbaiki bug fitur 1 @work
(A) Perbaiki bug fitur 2 @work
</code></pre></div></div>

<p>Hal yang sama juga bisa diimplementasikan ke konteks/project lainnya. Dengan ini, mengatur prioritas pekerjaan jadi lebih mudah, cepat, dan yang paling penting: gratis. <em>No bloated software</em>. <em>Just plaintext productivity</em>.</p>

<h2 id="neotodotxtnvim-atur-todotxt-di-neovim"><code class="language-plaintext highlighter-rouge">neotodotxt.nvim</code>: atur <code class="language-plaintext highlighter-rouge">todo.txt</code> di NeoVim</h2>

<p>Ini adalah plugin buatan saya sendiri. Kalau kamu NeoVim user, dan prefer mengatur <code class="language-plaintext highlighter-rouge">todo.txt</code> di NeoVim ketimbang CLI, plugin ini mungkin cocok buat kamu.</p>

<p>Tentu saja, karena <code class="language-plaintext highlighter-rouge">todo.txt</code> ini cuma file biasa, kamu tetap bisa menggunakan vim keybinding untuk melakukan manipulasi terhadap <code class="language-plaintext highlighter-rouge">todo.txt</code> kamu dengan bebas, entah itu memindahkana atau menghapus todo.</p>

<p>Untuk pengalaman terbaik, saya sarankan install plugin Treesitter dan nyalakan syntax hightlight untuk file <code class="language-plaintext highlighter-rouge">todo.txt</code> dengan cara menambahkan <code class="language-plaintext highlighter-rouge">todotxt</code> ke dalam <code class="language-plaintext highlighter-rouge">ensure_installed</code>.</p>

<p>Tambahkan kode berikut di <code class="language-plaintext highlighter-rouge">init.lua</code> supaya syntax highlight langsung berfungsi ketika membuka file <code class="language-plaintext highlighter-rouge">todo.txt</code> dan <code class="language-plaintext highlighter-rouge">done.txt</code>.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vim</span><span class="p">.</span><span class="n">api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">({</span><span class="s2">"BufRead"</span><span class="p">,</span> <span class="s2">"BufNewFile"</span><span class="p">},</span> <span class="p">{</span>
  <span class="n">pattern</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"todo.txt"</span><span class="p">,</span> <span class="s2">"done.txt"</span> <span class="p">},</span>
  <span class="n">callback</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
    <span class="n">vim</span><span class="p">.</span><span class="n">cmd</span><span class="p">(</span><span class="s">[[
      set filetype=todotxt
      set wrap
      set linebreak
    ]]</span><span class="p">)</span>
  <span class="k">end</span><span class="p">,</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Pluginnya bisa diakses <a href="https://github.com/adiprnm/neotodotxt.nvim">di sini</a>.</p>

<h2 id="markor-todotxt-di-smartphone">Markor: todo.txt di smartphone</h2>

<p>Markor adalah salah satu aplikasi note-taking yang gratis dan open source. Selain note-taking app, Markor juga mendukung format <code class="language-plaintext highlighter-rouge">todo.txt</code>. Saya pernah membahas seputar markor di tulisan <a href="/plaintext-productivity">Plaintext productivity</a>.</p>

<p>Untuk masalah filtering, Markor ini memang belum se-advance <code class="language-plaintext highlighter-rouge">todo.sh</code>. Tapi, saya rasa sangat cukup karena kita masih bisa melakukan pengurutan berdasarkan prioritas, project, konteks, tanggal dibuat, tenggat waktu, deskripsi, dan baris teks.</p>

<p>Enaknyaa lagi, di Markor sudah tersedia toolbar yang sangat memudahkan untuk melakukan manipulasi terhadap <code class="language-plaintext highlighter-rouge">todo.txt</code>, seperti memindahkan satu baris ke atas/bawah, menyisipkan project/konteks/tenggat waktu, menyelesaikan dan mengarsipkan todo yang sudah selesai, dan operasi-operasi lainnya. Urutan toolbar-nya pun bisa dikonfigurasi di <strong>Lainnya &gt; Preferensi &gt; todo.txt &gt; Perintah tindakan</strong></p>

<p>Sayangnya, sekarang aplikasi tersebut sudah tidak ada di Play Store. Tapi tenang, aplikasi tersebut masih bisa diunduh di <a href="https://f-droid.org/id/packages/net.gsantner.markor/" target="_blank">F-Droid</a>.</p>

<h2 id="sinkronisasi-antarperangkat-dengan-git">Sinkronisasi antarperangkat dengan Git</h2>

<p>Karena data 100% milik kita, kalau mau, kita bisa melakukan sinkronisasi <code class="language-plaintext highlighter-rouge">todo.txt</code> antarperangkat menggunakan Git dan GitHub. Prinsip dasarnya sama persis dengan tulisan yang pernah saya buat tentang <a href="/multi-device-password-management-dengan-password-store">Multi-device password management dengan Password Store</a>.</p>

<h2 id="penutup">Penutup</h2>

<p>Itulah sedikit tentang <code class="language-plaintext highlighter-rouge">todo.txt</code>. Sekarang saya sudah beralih menggunakan <code class="language-plaintext highlighter-rouge">todo.txt</code> untuk mencatat semua hal yang harus/ingin saya kerjakan. Sejauh ini sangat membantu untuk menentukan prioritas pekerjaan kantor dan pekerjaan-pekerjaan lainnya.</p>

<p>Kamu tertarik? Silakan dicoba. Seru kok, hehe.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><category term="Productivity" /><summary type="html"><![CDATA[Beberapa orang rela membayar biaya langganan hanya untuk aplikasi sederhana seperti to-do list. Padahal, aplikasi to-do list intinya ya begitu-begitu aja. Tambah todo, kasih beberapa atribute seperti label, dsb. Lalu atur prioritas mana yang harus diselesaikan terlebih dahulu atau masih bisa dilakukan di lain waktu. Setelah selesai, tandai to-do tersebut sebagai selesai.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Menambahkan notifikasi low battery di i3wm</title><link href="https://adipurnm.com/menambahkan-notifikasi-low-battery-di-i3wm" rel="alternate" type="text/html" title="Menambahkan notifikasi low battery di i3wm" /><published>2026-02-14T00:00:00+00:00</published><updated>2026-02-14T00:00:00+00:00</updated><id>https://adipurnm.com/menambahkan-notifikasi-low-battery-di-i3wm</id><content type="html" xml:base="https://adipurnm.com/menambahkan-notifikasi-low-battery-di-i3wm"><![CDATA[<p>Di customizable window manager seperti i3wm, segala hal yang berkaitan dengan desktop environment harus kita konfigurasikan. Salah satu hal yang perlu diatur konfigurasinya adalah notifikasi low battery.</p>

<p>Sudah beberapa kali saya tidak sadar kalau baterai laptop saya di bawah 20%, dan berujung pada laptop yang tiba-tiba mati sendiri. Menyebalkan, pastinya. Apalagi kalau kita sedang mengerjakan pekerjaan penting.</p>

<p>Saya minta GLM 4.7 untuk buatkan bash script yang akan memunculkan notifikasi low battery ketika persentase baterai di bawah 11%. Saya modifikasi sedikit untuk handle beberapa case yang belum ke-cover sepenuhnya oleh GLM.</p>

<p>Berikut script-nya:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Configuration</span>
<span class="nv">BATTERY_PATH</span><span class="o">=</span><span class="s2">"/sys/class/power_supply/BAT*"</span>
<span class="nv">LOW_BATTERY_THRESHOLD</span><span class="o">=</span>11
<span class="nv">CHECK_INTERVAL</span><span class="o">=</span>1
<span class="nv">SOUND_THEME</span><span class="o">=</span><span class="s2">"/usr/share/sounds/Yaru/stereo"</span>
<span class="nv">SOUND_LOW_BATTERY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">SOUND_THEME</span><span class="k">}</span><span class="s2">/battery-low.oga"</span>
<span class="nv">SOUND_POWER_PLUG</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">SOUND_THEME</span><span class="k">}</span><span class="s2">/power-plug.oga"</span>
<span class="nv">SOUND_POWER_UNPLUG</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">SOUND_THEME</span><span class="k">}</span><span class="s2">/power-unplug.oga"</span>
<span class="nv">NOTIFICATION_COOLDOWN</span><span class="o">=</span>300

<span class="nv">last_low_battery_notification</span><span class="o">=</span>0

read_battery_info<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">capacity_file</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="k">${</span><span class="nv">BATTERY_PATH</span><span class="k">}</span>/capacity 2&gt;/dev/null | <span class="nb">head</span> <span class="nt">-1</span><span class="si">)</span>
  <span class="nb">local </span><span class="nv">status_file</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="k">${</span><span class="nv">BATTERY_PATH</span><span class="k">}</span>/status 2&gt;/dev/null | <span class="nb">head</span> <span class="nt">-1</span><span class="si">)</span>

  <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$capacity_file</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$capacity_file</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"ERROR: Battery not found"</span> <span class="o">&gt;</span>&amp;2
    <span class="k">return </span>1
  <span class="k">fi

  </span><span class="nv">capacity</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$capacity_file</span><span class="s2">"</span> 2&gt;/dev/null<span class="si">)</span>
  <span class="nv">status</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$status_file</span><span class="s2">"</span> 2&gt;/dev/null<span class="si">)</span>

  <span class="k">return </span>0
<span class="o">}</span>

play_sound<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">sound_file</span><span class="o">=</span><span class="nv">$1</span>
  <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$sound_file</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span>paplay <span class="s2">"</span><span class="nv">$sound_file</span><span class="s2">"</span> &amp;
  <span class="k">fi</span>
<span class="o">}</span>

send_notification<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">urgency</span><span class="o">=</span><span class="nv">$1</span>
  <span class="nb">local </span><span class="nv">title</span><span class="o">=</span><span class="nv">$2</span>
  <span class="nb">local </span><span class="nv">message</span><span class="o">=</span><span class="nv">$3</span>
  notify-send <span class="nt">-u</span> <span class="s2">"</span><span class="nv">$urgency</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$title</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$message</span><span class="s2">"</span>
<span class="o">}</span>

<span class="nv">status_file</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="k">${</span><span class="nv">BATTERY_PATH</span><span class="k">}</span>/status 2&gt;/dev/null | <span class="nb">head</span> <span class="nt">-1</span><span class="si">)</span>
<span class="nv">prev_status</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$status_file</span><span class="s2">"</span> 2&gt;/dev/null<span class="si">)</span>

<span class="k">while </span><span class="nb">true</span><span class="p">;</span> <span class="k">do
  if</span> <span class="o">!</span> read_battery_info<span class="p">;</span> <span class="k">then
    </span><span class="nb">sleep</span> <span class="s2">"</span><span class="nv">$CHECK_INTERVAL</span><span class="s2">"</span>
    <span class="k">continue
  fi

  </span><span class="nv">current_time</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +%s<span class="si">)</span>

  <span class="nb">echo</span> <span class="nv">$prev_status</span>
  <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$status</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"Discharging"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$prev_status</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"Discharging"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
      </span>play_sound <span class="s2">"</span><span class="nv">$SOUND_POWER_UNPLUG</span><span class="s2">"</span>
    <span class="k">elif</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$capacity</span><span class="s2">"</span> <span class="nt">-lt</span> <span class="s2">"</span><span class="nv">$LOW_BATTERY_THRESHOLD</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
      </span><span class="nv">time_since_last_notification</span><span class="o">=</span><span class="k">$((</span>current_time <span class="o">-</span> last_low_battery_notification<span class="k">))</span>
      <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$time_since_last_notification</span><span class="s2">"</span> <span class="nt">-ge</span> <span class="s2">"</span><span class="nv">$NOTIFICATION_COOLDOWN</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span>send_notification <span class="s2">"critical"</span> <span class="s2">"Battery Low"</span> <span class="s2">"Battery at </span><span class="k">${</span><span class="nv">capacity</span><span class="k">}</span><span class="s2">%"</span>
        <span class="nv">last_low_battery_notification</span><span class="o">=</span><span class="nv">$current_time</span>
      <span class="k">fi
      </span>play_sound <span class="s2">"</span><span class="nv">$SOUND_LOW_BATTERY</span><span class="s2">"</span>
      <span class="nb">sleep </span>2
    <span class="k">fi
  elif</span> <span class="o">[[</span> <span class="o">(</span><span class="s2">"</span><span class="nv">$status</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"Charging"</span> <span class="o">||</span> <span class="s2">"</span><span class="nv">$status</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"Not charging"</span> <span class="o">||</span> <span class="s2">"</span><span class="nv">$status</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"Full"</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="s2">"</span><span class="nv">$prev_status</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"Discharging"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>play_sound <span class="s2">"</span><span class="nv">$SOUND_POWER_PLUG</span><span class="s2">"</span>
  <span class="k">fi

  </span><span class="nv">prev_status</span><span class="o">=</span><span class="s2">"</span><span class="nv">$status</span><span class="s2">"</span>
  <span class="nb">sleep</span> <span class="s2">"</span><span class="nv">$CHECK_INTERVAL</span><span class="s2">"</span>
<span class="k">done</span>
</code></pre></div></div>

<p>Script ini disimpan di <code class="language-plaintext highlighter-rouge">~/.config/i3/scripts/battery-notify.sh</code>. Script ini kemungkinan ditambahkan ke file <code class="language-plaintext highlighter-rouge">~/.config/i3/config</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># CHECK FOR BATTERY PERCENTAGE. NOTIFY WHEN LOW
exec --no-startup-id "sleep 2; ~/.config/i3/scripts/battery-notify.sh &amp;"
</code></pre></div></div>

<p>Kenapa harus sleep selama 2 detik dulu sebelum menjalankan script? Hanya untuk memastikan bahwa proses Dunst (program untuk notifikasi) sudah berjalan sebelum script <code class="language-plaintext highlighter-rouge">battery-notify.sh</code> dieksekusi, sehingga notifikasinya pakai notifikasi yang tampilannya sudah saya custom ketimbang pakai notifikasi bawaan Ubuntu MATE.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><summary type="html"><![CDATA[Di customizable window manager seperti i3wm, segala hal yang berkaitan dengan desktop environment harus kita konfigurasikan. Salah satu hal yang perlu diatur konfigurasinya adalah notifikasi low battery.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Buat post baru di Jekyll dengan bash script</title><link href="https://adipurnm.com/buat-post-baru-di-jekyll-dengan-bash-script" rel="alternate" type="text/html" title="Buat post baru di Jekyll dengan bash script" /><published>2026-02-13T00:00:00+00:00</published><updated>2026-02-13T00:00:00+00:00</updated><id>https://adipurnm.com/buat-post-baru-di-jekyll-dengan-bash-script</id><content type="html" xml:base="https://adipurnm.com/buat-post-baru-di-jekyll-dengan-bash-script"><![CDATA[<p>Setiap kali mau buat post baru di Jekyll, yang saya lakukan itu begini:</p>

<ol>
  <li>Buka NeoVim</li>
  <li>Buat file markdown baru di folder <code class="language-plaintext highlighter-rouge">_posts</code></li>
  <li>Copy frontmatter dari post sebelumnya dan sesuaikan isinya</li>
  <li>Mulai menulis</li>
</ol>

<p>Ada beberapa pain point yang saya rasakan dengan flow ini:</p>

<ol>
  <li>Harus ngetik nama file manual. Format file post di Jekyll itu kan <code class="language-plaintext highlighter-rouge">yyyy-mm-dd-slug.md</code>. Jadi bayangkan, setiap kali mau bikin post, saya harus ketik manual itu semua. Belum lagi kalau judulnya cukup panjang. Pernah juga beberapa kali tulisan sudah dibuat dan di-push ke GitHub, tapi tulisan tidak muncul di blog karena ternyata tanggalnya typo jadi future date.</li>
  <li>Folder <code class="language-plaintext highlighter-rouge">_posts</code> itu ada di tengah-tengah. Karena saya buka di NeoVim, ada beberapa extra step untuk masuk ke dalam folder dan membuat file di dalam folder tersebut.</li>
</ol>

<p>Karena flow-nya gitu-gitu aja, saya automate aja pakai bash script. Karena skill bash script saya terbatas, saya minta <a href="/langganan-glm-4.7">AI</a> buatkan script-nya.</p>

<p>Jadi, untuk buat post baru, cukup ketik perintah <code class="language-plaintext highlighter-rouge">new-post "Judul post"</code>, dan file akan otomatis masuk ke dalam folder <code class="language-plaintext highlighter-rouge">_posts</code> dengan format file post yang saya tulis di poin 1. Tanggalnya akan selalu di-set ke tanggal perintah tersebut dijalankan.</p>

<p>Kalau mau buat draft post dulu, cukup ditambah flag <code class="language-plaintext highlighter-rouge">--draft</code>, sehingga perintahnya jadi <code class="language-plaintext highlighter-rouge">new-post "Judul post" --draft</code></p>

<p>Saya heran juga kenapa baru kepikiran baru-baru ini, haha. Padahal tahun 2025 saya lagi rajin-rajinnya nulis blog post.</p>

<p>Berikut script yang saya pakai. Simpan di dalam folder yang terdaftar dalam <code class="language-plaintext highlighter-rouge">PATH</code> dengan namaa <code class="language-plaintext highlighter-rouge">new-post</code>, misalnya kalau di Linux itu <code class="language-plaintext highlighter-rouge">~/.local/bin</code></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># Jekyll Post Generator</span>
<span class="c"># Usage: ./new-post.sh "Post Title" [tag1,tag2,tag3] [--draft]</span>

<span class="nb">set</span> <span class="nt">-e</span>

<span class="c"># Default values</span>
<span class="nv">POST_DIR</span><span class="o">=</span><span class="s2">"_posts"</span>
<span class="nv">DRAFT_DIR</span><span class="o">=</span><span class="s2">"_drafts"</span>
<span class="nv">IS_DRAFT</span><span class="o">=</span><span class="nb">false
</span><span class="nv">DATE</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +%Y-%m-%d<span class="si">)</span>

<span class="c"># Parse arguments</span>
<span class="nv">TITLE</span><span class="o">=</span><span class="s2">""</span>
<span class="nv">TAGS</span><span class="o">=</span><span class="s2">""</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Usage: ./new-post.sh </span><span class="se">\"</span><span class="s2">Post Title</span><span class="se">\"</span><span class="s2"> [tag1,tag2,tag3] [--draft]"</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nv">TITLE</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"--draft"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">IS_DRAFT</span><span class="o">=</span><span class="nb">true
  </span><span class="k">else
    </span><span class="nv">TAGS</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
  <span class="k">fi
fi

if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"--draft"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">IS_DRAFT</span><span class="o">=</span><span class="nb">true
  </span><span class="k">fi
fi</span>

<span class="c"># Convert title to filename-safe format</span>
<span class="nv">FILENAME</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$TITLE</span><span class="s2">"</span> | <span class="nb">tr</span> <span class="s1">'[:upper:]'</span> <span class="s1">'[:lower:]'</span> | <span class="nb">sed</span> <span class="s1">'s/[^a-z0-9]/-/g'</span> | <span class="nb">sed</span> <span class="s1">'s/--*/-/g'</span> | <span class="nb">sed</span> <span class="s1">'s/^-\|-$//g'</span><span class="si">)</span>

<span class="c"># Set directory and filename</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$IS_DRAFT</span><span class="s2">"</span> <span class="o">=</span> <span class="nb">true</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nv">FILENAME</span><span class="o">=</span><span class="s2">"</span><span class="nv">$DRAFT_DIR</span><span class="s2">/</span><span class="k">${</span><span class="nv">DATE</span><span class="k">}</span><span class="s2">-</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">.md"</span>
<span class="k">else
  </span><span class="nv">FILENAME</span><span class="o">=</span><span class="s2">"</span><span class="nv">$POST_DIR</span><span class="s2">/</span><span class="k">${</span><span class="nv">DATE</span><span class="k">}</span><span class="s2">-</span><span class="k">${</span><span class="nv">FILENAME</span><span class="k">}</span><span class="s2">.md"</span>
<span class="k">fi</span>

<span class="c"># Check if file already exists</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$FILENAME</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Error: File already exists: </span><span class="nv">$FILENAME</span><span class="s2">"</span>
  <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># Create the post file</span>
<span class="nb">cat</span> <span class="o">&gt;</span> <span class="s2">"</span><span class="nv">$FILENAME</span><span class="s2">"</span> <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
---
layout: default
title: </span><span class="nv">$TITLE</span><span class="sh">
tags: </span><span class="nv">$TAGS</span><span class="sh">
---
</span><span class="no">
EOF

</span><span class="nb">echo</span> <span class="s2">"Created: </span><span class="nv">$FILENAME</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"You can now edit your new post!"</span>
</code></pre></div></div>

<p>Kalau kamu pakai static site generator lain yang direktorinya berbeda, silahkan minta AI saja untuk sesuaikan, hehe.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><summary type="html"><![CDATA[Setiap kali mau buat post baru di Jekyll, yang saya lakukan itu begini:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Multi-device password management dengan Password Store</title><link href="https://adipurnm.com/multi-device-password-management-dengan-password-store" rel="alternate" type="text/html" title="Multi-device password management dengan Password Store" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://adipurnm.com/multi-device-password-management-dengan-password-store</id><content type="html" xml:base="https://adipurnm.com/multi-device-password-management-dengan-password-store"><![CDATA[<p>Beberapa waktu lalu saya mulai menaruh perhatian terhadap aplikasi password manager. Ada beberapa layanan yang bisa digunakan dengan biaya layanan yang miring, bahkan gratis. Tapi, bukan Adi namanya kalau tidak mempersulit diri, haha.</p>

<p>Untuk kasus password manager, saya ingin kepemilikan 100%. Sama persis seperti blog. Jadi, nggak bergantung ke satu layanan tertentu.</p>

<p>Saya teringat mas Rizqi, penulis blog <a href="https://bandithijo.dev" target="_blank">BanditHijo</a>, pernah mention password manager bernama <code class="language-plaintext highlighter-rouge">pass</code>. Sepertinya cukup menarik untuk dicoba.</p>

<p>Ternyata, kabar baiknya pass ini ada aplikasi Android-nya. Owner aslinya udah nggak maintain sih, tapi ada orang baik yang bersedia maintain dan fork repo-nya <a href="https://github.com/agrahn/Android-Password-Store" target="_blank">di sini</a>.</p>

<p>Dengan ini, keinginan saya untuk memiliki 100% encrypted password saya sendiri dapat direalisasikan.</p>

<p>Untuk proses sinkronisasi password antarperangkat, saya pakai Git. Jadi, password saya tersimpan di sebuah private repository di GitHub. Lalu, setiap ada perubahan, saya push perubahannya ke GitHub. Di masing-masing device, bisa lakukan sync password supaya tetap up-to-date.</p>

<p>Jadi:</p>
<ul>
  <li>untuk auto-fill password di browser, saya pakai plugin Browserpass</li>
  <li>untuk auto-fill password di smartphone saya pakai aplikasi Android Password Store.</li>
</ul>

<p>Setupnya agak cukup ribet, karena harus melakukan step-step berikut:</p>
<ul>
  <li>generate GPG key dulu</li>
  <li>initialize password store pakai GPG key</li>
  <li>tambahkan beberapa password ke password store</li>
  <li>buat private repository di GitHub</li>
  <li>push changesnya ke GitHub</li>
  <li>import secret key di smartphone</li>
  <li>generate SSH public &amp; private key</li>
  <li>simpan public SSH key di akun GitHub</li>
  <li>clone private repository github di smartphone</li>
  <li>password store siap digunakan</li>
</ul>

<p>Bagi orang yang nggak mau ribet, saran saya sudah pakai aja layanan yang udah ada, macam Bitwarden atau 1Password. Tapi kalau emang mau ribet, silahkan pakai cara saya ini. Kalau kurang lengkap, tanya-tanya AI sedikit harusnya akan membantu.</p>

<h2 id="gotcha-android-password-store-nggak-works-di-google-chrome">Gotcha: Android Password Store nggak works di Google Chrome</h2>

<p>Ini ngeselin sih memang. Udah coba segala cara, masih saja gagal dipakai. Padahal, di Firefox dan Brave. auto fill Android Password Store fiturnya berfungsi dengan baik.</p>

<p>Saran saya, stop cari cara supaya bisa works. Ganti browser default aja jadi Brave Browser atau browser lain yang bisa pakai auto-fill username dan password.</p>]]></content><author><name>Adi Purnama</name></author><category term="Tech" /><summary type="html"><![CDATA[Beberapa waktu lalu saya mulai menaruh perhatian terhadap aplikasi password manager. Ada beberapa layanan yang bisa digunakan dengan biaya layanan yang miring, bahkan gratis. Tapi, bukan Adi namanya kalau tidak mempersulit diri, haha.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://adipurnm.com/assets/images/adi-og.jpg" /><media:content medium="image" url="https://adipurnm.com/assets/images/adi-og.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>