[{"content":"zypper adalah package manager bawaan openSUSE. Hampir semua perintahnya punya singkatan, jadi di tabel ini saya tulis keduanya. Perintah yang mengubah sistem perlu sudo.\nUpdate sistem Perintah Fungsi zypper refresh / zypper ref Perbarui metadata semua repositori zypper dup Upgrade penuh. Cara yang benar untuk Tumbleweed zypper update / zypper up Update paket terpasang. Dipakai di Leap zypper patch Pasang patch resmi (Leap) zypper list-updates / zypper lu Lihat paket yang bisa di-update zypper list-patches / zypper lp Lihat patch yang tersedia zypper ps -s Lihat proses yang masih memakai file lama setelah update zypper needs-rebooting Cek apakah perlu reboot # Rutinitas update di Tumbleweed sudo zypper ref \u0026amp;\u0026amp; sudo zypper dup Cari dan pasang paket Perintah Fungsi zypper search nama / zypper se nama Cari paket zypper se -i nama Cari di antara paket yang sudah terpasang zypper se --provides /usr/bin/dig Cari paket yang menyediakan file tertentu zypper info nama / zypper if nama Detail paket zypper install nama / zypper in nama Pasang paket zypper in ./paket.rpm Pasang file RPM lokal beserta dependensinya zypper in -t pattern devel_basis Pasang pattern (kumpulan paket) zypper remove nama / zypper rm nama Hapus paket zypper rm -u nama Hapus paket sekaligus dependensi yang tidak lagi dipakai zypper in -f nama Pasang ulang paket (force) Repositori Perintah Fungsi zypper repos -d / zypper lr -d Daftar repositori beserta URL zypper addrepo -f URL alias / zypper ar -f URL alias Tambah repo dengan auto-refresh zypper removerepo alias / zypper rr alias Hapus repo zypper modifyrepo -d alias / zypper mr -d alias Nonaktifkan repo zypper mr -e alias Aktifkan lagi repo zypper mr -p 90 alias Ubah prioritas (angka kecil = prioritas lebih tinggi) zypper dup --from alias Upgrade/ganti vendor paket dari repo tertentu Untuk memasang paket dari Open Build Service (OBS) dengan lebih mudah, coba opi:\nsudo zypper in opi opi nama-paket Kunci paket (lock) Perintah Fungsi zypper addlock nama / zypper al nama Kunci paket supaya tidak di-update atau dihapus zypper locks / zypper ll Daftar lock zypper removelock nama / zypper rl nama Lepas lock Perawatan Perintah Fungsi zypper clean -a Bersihkan cache paket dan metadata zypper packages --orphaned Paket yang tidak lagi ada di repo mana pun zypper packages --unneeded Paket yang tidak dibutuhkan paket lain zypper verify / zypper ve Cek dan perbaiki dependensi yang rusak zypper -n in nama Mode non-interaktif, cocok untuk script Upgrade versi Leap # Contoh: naik ke Leap 15.6. Pastikan semua repo pakai variabel $releasever dulu. sudo zypper --releasever=15.6 ref sudo zypper --releasever=15.6 dup Sebelum dup, pastikan snapper aktif supaya kamu bisa rollback kalau ada yang rusak. Lihat cheat sheet Snapper dan Btrfs.\n","permalink":"https://rifkiaz.my.id/cheatsheet/zypper/","summary":"\u003cp\u003e\u003ccode\u003ezypper\u003c/code\u003e adalah package manager bawaan openSUSE. Hampir semua perintahnya punya singkatan, jadi di tabel ini saya tulis keduanya. Perintah yang mengubah sistem perlu \u003ccode\u003esudo\u003c/code\u003e.\u003c/p\u003e\n\u003ch2 id=\"update-sistem\"\u003eUpdate sistem\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper refresh\u003c/code\u003e / \u003ccode\u003ezypper ref\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePerbarui metadata semua repositori\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper dup\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUpgrade penuh. \u003cstrong\u003eCara yang benar untuk Tumbleweed\u003c/strong\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper update\u003c/code\u003e / \u003ccode\u003ezypper up\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUpdate paket terpasang. Dipakai di Leap\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper patch\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang patch resmi (Leap)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper list-updates\u003c/code\u003e / \u003ccode\u003ezypper lu\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat paket yang bisa di-update\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper list-patches\u003c/code\u003e / \u003ccode\u003ezypper lp\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat patch yang tersedia\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper ps -s\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat proses yang masih memakai file lama setelah update\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper needs-rebooting\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCek apakah perlu reboot\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Rutinitas update di Tumbleweed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ref \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo zypper dup\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"cari-dan-pasang-paket\"\u003eCari dan pasang paket\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper search nama\u003c/code\u003e / \u003ccode\u003ezypper se nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari paket\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper se -i nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari di antara paket yang sudah terpasang\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper se --provides /usr/bin/dig\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari paket yang menyediakan file tertentu\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper info nama\u003c/code\u003e / \u003ccode\u003ezypper if nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDetail paket\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper install nama\u003c/code\u003e / \u003ccode\u003ezypper in nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang paket\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper in ./paket.rpm\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang file RPM lokal beserta dependensinya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper in -t pattern devel_basis\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang pattern (kumpulan paket)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper remove nama\u003c/code\u003e / \u003ccode\u003ezypper rm nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus paket\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper rm -u nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus paket sekaligus dependensi yang tidak lagi dipakai\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper in -f nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang ulang paket (force)\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"repositori\"\u003eRepositori\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper repos -d\u003c/code\u003e / \u003ccode\u003ezypper lr -d\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar repositori beserta URL\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper addrepo -f URL alias\u003c/code\u003e / \u003ccode\u003ezypper ar -f URL alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTambah repo dengan auto-refresh\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper removerepo alias\u003c/code\u003e / \u003ccode\u003ezypper rr alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus repo\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper modifyrepo -d alias\u003c/code\u003e / \u003ccode\u003ezypper mr -d alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eNonaktifkan repo\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper mr -e alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eAktifkan lagi repo\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper mr -p 90 alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUbah prioritas (angka kecil = prioritas lebih tinggi)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezypper dup --from alias\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUpgrade/ganti vendor paket dari repo tertentu\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eUntuk memasang paket dari Open Build Service (OBS) dengan lebih mudah, coba \u003ccode\u003eopi\u003c/code\u003e:\u003c/p\u003e","title":"Zypper"},{"content":"Instalasi default openSUSE memakai Btrfs untuk root dan Snapper untuk snapshot. Setiap kali kamu menjalankan zypper atau YaST, snapshot pre dan post otomatis dibuat. Ini yang bikin rollback di openSUSE gampang banget.\nSnapshot Perintah Fungsi snapper list Daftar snapshot snapper create -d \u0026quot;sebelum ganti kernel\u0026quot; Buat snapshot manual dengan deskripsi snapper status 10..12 File apa saja yang berubah di antara dua snapshot snapper diff 10..12 /etc/hosts Diff isi file di antara dua snapshot snapper undochange 10..12 /etc/hosts Kembalikan file tertentu ke kondisi snapshot 10 snapper delete 15 Hapus satu snapshot snapper delete 15-20 Hapus rentang snapshot Rollback sistem Kalau setelah update sistem tidak bisa boot:\nDi menu GRUB, pilih Start bootloader from a read-only snapshot. Pilih snapshot sebelum update, lalu boot. Setelah masuk dan semuanya normal, jadikan permanen: sudo snapper rollback sudo reboot Kalau sistem masih bisa boot, kamu juga bisa langsung rollback ke nomor snapshot tertentu:\nsudo snapper rollback 42 sudo reboot Konfigurasi Perintah Fungsi snapper list-configs Daftar konfigurasi (biasanya cuma root) snapper -c home create-config /home Buat konfigurasi snapshot untuk /home (harus subvolume Btrfs) snapper -c home list Daftar snapshot untuk konfigurasi home snapper get-config Lihat isi konfigurasi root Konfigurasi ada di /etc/snapper/configs/root. Beberapa opsi yang sering diubah:\nNUMBER_LIMIT=\u0026#34;2-10\u0026#34; # jumlah snapshot pre/post yang disimpan NUMBER_LIMIT_IMPORTANT=\u0026#34;4-10\u0026#34; TIMELINE_CREATE=\u0026#34;no\u0026#34; # snapshot per jam; matikan kalau disk kecil Pembersihan otomatis dijalankan oleh snapper-cleanup.timer. Cek dengan systemctl status snapper-cleanup.timer.\nBtrfs Perintah Fungsi btrfs filesystem usage / Pemakaian disk yang akurat (lebih jujur daripada df) btrfs subvolume list / Daftar subvolume btrfs filesystem df / Ringkasan pemakaian per tipe (data, metadata) btrfs balance start -dusage=50 / Rapikan blok data yang terisi di bawah 50% btrfs scrub start / Cek integritas data di latar belakang btrfs scrub status / Lihat progres dan hasil scrub btrfs device stats / Statistik error perangkat Paket btrfsmaintenance menjalankan balance dan scrub secara terjadwal. Pengaturannya ada di /etc/sysconfig/btrfsmaintenance.\nDisk penuh gara-gara snapshot? sudo snapper list # lihat snapshot lama sudo snapper delete 100-150 # hapus yang tidak perlu sudo btrfs balance start -dusage=50 / # kembalikan ruang sudo btrfs filesystem usage / ","permalink":"https://rifkiaz.my.id/cheatsheet/snapper-btrfs/","summary":"\u003cp\u003eInstalasi default openSUSE memakai Btrfs untuk root dan Snapper untuk snapshot. Setiap kali kamu menjalankan \u003ccode\u003ezypper\u003c/code\u003e atau YaST, snapshot \u003cem\u003epre\u003c/em\u003e dan \u003cem\u003epost\u003c/em\u003e otomatis dibuat. Ini yang bikin rollback di openSUSE gampang banget.\u003c/p\u003e\n\u003ch2 id=\"snapshot\"\u003eSnapshot\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper list\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar snapshot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper create -d \u0026quot;sebelum ganti kernel\u0026quot;\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBuat snapshot manual dengan deskripsi\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper status 10..12\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eFile apa saja yang berubah di antara dua snapshot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper diff 10..12 /etc/hosts\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDiff isi file di antara dua snapshot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper undochange 10..12 /etc/hosts\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eKembalikan file tertentu ke kondisi snapshot 10\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper delete 15\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus satu snapshot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esnapper delete 15-20\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus rentang snapshot\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"rollback-sistem\"\u003eRollback sistem\u003c/h2\u003e\n\u003cp\u003eKalau setelah update sistem tidak bisa boot:\u003c/p\u003e","title":"Snapper dan Btrfs"},{"content":"openSUSE memakai systemd, jadi contekan ini juga berlaku di hampir semua distro modern.\nService Perintah Fungsi systemctl status sshd Status service beserta potongan log terakhir systemctl start sshd Jalankan service systemctl stop sshd Hentikan service systemctl restart sshd Restart service systemctl reload sshd Muat ulang konfigurasi tanpa restart (kalau didukung) systemctl enable --now sshd Aktifkan saat boot sekaligus jalankan sekarang systemctl disable --now sshd Matikan saat boot sekaligus hentikan systemctl mask nama Blokir service supaya tidak bisa dijalankan sama sekali systemctl is-active sshd Cek cepat, cocok untuk script systemctl is-enabled sshd Cek apakah aktif saat boot Melihat dan mengubah unit Perintah Fungsi systemctl list-units --type=service Service yang sedang dimuat systemctl --failed Unit yang gagal systemctl list-unit-files --state=enabled Unit yang aktif saat boot systemctl list-timers Daftar timer (pengganti cron) systemctl cat sshd Lihat isi file unit systemctl edit sshd Buat override tanpa mengubah file aslinya systemctl daemon-reload Muat ulang unit setelah mengubah file secara manual Log dengan journalctl Perintah Fungsi journalctl -u sshd Log satu service journalctl -u sshd -f Ikuti log secara live journalctl -b Log sejak boot terakhir journalctl -b -1 Log boot sebelumnya, berguna setelah crash journalctl -p err -b Hanya error ke atas sejak boot journalctl --since \u0026quot;1 hour ago\u0026quot; Log satu jam terakhir journalctl -k Log kernel (seperti dmesg) journalctl --disk-usage Ukuran journal di disk journalctl --vacuum-time=2weeks Hapus log yang lebih lama dari dua minggu Pengaturan sistem Perintah Fungsi hostnamectl set-hostname nama-host Ganti hostname timedatectl set-timezone Asia/Jakarta Ganti zona waktu timedatectl Cek waktu dan status sinkronisasi NTP localectl set-keymap us Ganti layout keyboard di konsol systemctl reboot / systemctl poweroff Reboot / matikan Diagnosis boot Perintah Fungsi systemd-analyze Total waktu boot systemd-analyze blame Service yang paling lama saat boot systemd-analyze critical-chain Rantai service yang memperlambat boot Contoh override Misalnya kamu ingin service otomatis restart kalau mati:\nsudo systemctl edit nama-service [Service] Restart=on-failure RestartSec=5 Simpan, lalu sudo systemctl restart nama-service. Override disimpan di /etc/systemd/system/nama-service.service.d/override.conf, jadi aman dari update paket.\n","permalink":"https://rifkiaz.my.id/cheatsheet/systemd/","summary":"\u003cp\u003eopenSUSE memakai systemd, jadi contekan ini juga berlaku di hampir semua distro modern.\u003c/p\u003e\n\u003ch2 id=\"service\"\u003eService\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl status sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eStatus service beserta potongan log terakhir\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl start sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eJalankan service\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl stop sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHentikan service\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl restart sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eRestart service\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl reload sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMuat ulang konfigurasi tanpa restart (kalau didukung)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl enable --now sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eAktifkan saat boot sekaligus jalankan sekarang\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl disable --now sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMatikan saat boot sekaligus hentikan\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl mask nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBlokir service supaya tidak bisa dijalankan sama sekali\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl is-active sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCek cepat, cocok untuk script\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl is-enabled sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCek apakah aktif saat boot\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"melihat-dan-mengubah-unit\"\u003eMelihat dan mengubah unit\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl list-units --type=service\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eService yang sedang dimuat\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl --failed\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUnit yang gagal\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl list-unit-files --state=enabled\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUnit yang aktif saat boot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl list-timers\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar timer (pengganti cron)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl cat sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat isi file unit\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl edit sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBuat override tanpa mengubah file aslinya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl daemon-reload\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMuat ulang unit setelah mengubah file secara manual\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"log-dengan-journalctl\"\u003eLog dengan journalctl\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -u sshd\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLog satu service\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -u sshd -f\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eIkuti log secara live\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -b\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLog sejak boot terakhir\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -b -1\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLog boot sebelumnya, berguna setelah crash\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -p err -b\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHanya error ke atas sejak boot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl --since \u0026quot;1 hour ago\u0026quot;\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLog satu jam terakhir\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl -k\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLog kernel (seperti \u003ccode\u003edmesg\u003c/code\u003e)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl --disk-usage\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUkuran journal di disk\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ejournalctl --vacuum-time=2weeks\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus log yang lebih lama dari dua minggu\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"pengaturan-sistem\"\u003ePengaturan sistem\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ehostnamectl set-hostname nama-host\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eGanti hostname\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etimedatectl set-timezone Asia/Jakarta\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eGanti zona waktu\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etimedatectl\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCek waktu dan status sinkronisasi NTP\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003elocalectl set-keymap us\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eGanti layout keyboard di konsol\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemctl reboot\u003c/code\u003e / \u003ccode\u003esystemctl poweroff\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eReboot / matikan\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"diagnosis-boot\"\u003eDiagnosis boot\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemd-analyze\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTotal waktu boot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemd-analyze blame\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eService yang paling lama saat boot\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esystemd-analyze critical-chain\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eRantai service yang memperlambat boot\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"contoh-override\"\u003eContoh override\u003c/h2\u003e\n\u003cp\u003eMisalnya kamu ingin service otomatis restart kalau mati:\u003c/p\u003e","title":"systemd dan journalctl"},{"content":"openSUSE punya dua pengelola jaringan. Desktop Tumbleweed dan Leap 16 memakai NetworkManager, sedangkan server Leap 15 umumnya memakai wicked. Cek mana yang aktif:\nsystemctl is-active NetworkManager wicked Contoh IP di bawah memakai rentang dokumentasi 192.0.2.0/24. Ganti dengan alamat di jaringanmu.\nPerintah dasar Perintah Fungsi ip a Daftar interface dan alamat IP ip r Tabel routing ip -s link Statistik paket per interface ss -tulpn Port yang sedang listen beserta prosesnya ping -c 4 opensuse.org Tes konektivitas tracepath opensuse.org Lihat rute ke tujuan dig opensuse.org Tes DNS (paket bind-utils) curl -I https://opensuse.org Cek respons HTTP NetworkManager (nmcli) Perintah Fungsi nmcli device status Status semua perangkat nmcli connection show Daftar koneksi tersimpan nmcli connection up \u0026quot;nama\u0026quot; Aktifkan koneksi nmcli connection down \u0026quot;nama\u0026quot; Matikan koneksi nmcli device wifi list Daftar Wi-Fi di sekitar nmcli device wifi connect SSID --ask Sambung ke Wi-Fi, password ditanya nmtui Antarmuka teks yang lebih ramah Set IP statis:\nsudo nmcli connection modify \u0026#34;Wired connection 1\u0026#34; \\ ipv4.method manual \\ ipv4.addresses 192.0.2.10/24 \\ ipv4.gateway 192.0.2.1 \\ ipv4.dns \u0026#34;1.1.1.1 9.9.9.9\u0026#34; sudo nmcli connection up \u0026#34;Wired connection 1\u0026#34; wicked Perintah Fungsi wicked show all Status semua interface wicked ifup eth0 Aktifkan interface wicked ifdown eth0 Matikan interface wicked ifreload all Terapkan perubahan konfigurasi Konfigurasinya ada di /etc/sysconfig/network/ifcfg-\u0026lt;interface\u0026gt;. Contoh IP statis:\nBOOTPROTO=\u0026#39;static\u0026#39; STARTMODE=\u0026#39;auto\u0026#39; IPADDR=\u0026#39;192.0.2.10/24\u0026#39; Gateway diatur di /etc/sysconfig/network/routes:\ndefault 192.0.2.1 - - DNS di openSUSE dikelola oleh netconfig, jadi jangan edit /etc/resolv.conf langsung. Atur NETCONFIG_DNS_STATIC_SERVERS di /etc/sysconfig/network/config, lalu jalankan sudo netconfig update -f.\nfirewalld Perintah Fungsi firewall-cmd --state Cek firewall berjalan firewall-cmd --get-active-zones Zona aktif dan interface-nya firewall-cmd --list-all Aturan di zona default firewall-cmd --add-service=http --permanent Buka service secara permanen firewall-cmd --add-port=8080/tcp --permanent Buka port tertentu firewall-cmd --remove-service=http --permanent Tutup lagi service firewall-cmd --reload Terapkan aturan permanen firewall-cmd --get-services Daftar nama service yang dikenal Aturan tanpa --permanent langsung berlaku tapi hilang setelah reload atau reboot. Cara aman: tes dulu tanpa --permanent, kalau sudah benar jalankan firewall-cmd --runtime-to-permanent.\n","permalink":"https://rifkiaz.my.id/cheatsheet/jaringan/","summary":"\u003cp\u003eopenSUSE punya dua pengelola jaringan. Desktop Tumbleweed dan Leap 16 memakai \u003cstrong\u003eNetworkManager\u003c/strong\u003e, sedangkan server Leap 15 umumnya memakai \u003cstrong\u003ewicked\u003c/strong\u003e. Cek mana yang aktif:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl is-active NetworkManager wicked\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003eContoh IP di bawah memakai rentang dokumentasi \u003ccode\u003e192.0.2.0/24\u003c/code\u003e. Ganti dengan alamat di jaringanmu.\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"perintah-dasar\"\u003ePerintah dasar\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eip a\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar interface dan alamat IP\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eip r\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTabel routing\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eip -s link\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eStatistik paket per interface\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ess -tulpn\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePort yang sedang listen beserta prosesnya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eping -c 4 opensuse.org\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTes konektivitas\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etracepath opensuse.org\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat rute ke tujuan\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edig opensuse.org\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTes DNS (paket \u003ccode\u003ebind-utils\u003c/code\u003e)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ecurl -I https://opensuse.org\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCek respons HTTP\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"networkmanager-nmcli\"\u003eNetworkManager (nmcli)\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli device status\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eStatus semua perangkat\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli connection show\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar koneksi tersimpan\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli connection up \u0026quot;nama\u0026quot;\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eAktifkan koneksi\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli connection down \u0026quot;nama\u0026quot;\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMatikan koneksi\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli device wifi list\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar Wi-Fi di sekitar\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmcli device wifi connect SSID --ask\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSambung ke Wi-Fi, password ditanya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003enmtui\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eAntarmuka teks yang lebih ramah\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eSet IP statis:\u003c/p\u003e","title":"Jaringan dan Firewall"},{"content":"YaST adalah alat administrasi khas openSUSE. Kelebihannya: hampir semua modulnya bisa dibuka di terminal (ncurses), jadi tetap enak dipakai lewat SSH.\nDi Leap 16, YaST tidak lagi disertakan. openSUSE beralih ke Cockpit untuk administrasi lewat browser dan Agama sebagai installer. Contekan ini berlaku untuk Tumbleweed dan Leap 15.x.\nDasar Perintah Fungsi sudo yast Pusat kontrol YaST versi teks sudo yast2 Pusat kontrol versi GUI (kalau ada desktop) yast -l Daftar semua modul yang terpasang sudo yast nama_modul Langsung buka modul tertentu Di mode teks, pindah antar-elemen pakai Tab, pilih pakai Enter atau Spasi, dan tombol fungsi seperti F10 (OK) atau F9 (keluar). Kalau F key ditangkap terminal, pakai Alt + huruf yang disorot.\nModul yang sering dipakai Modul Fungsi sudo yast sw_single Cari, pasang, dan hapus paket sudo yast online_update Pasang patch (Leap) sudo yast repositories Kelola repositori sudo yast lan Pengaturan jaringan (wicked) sudo yast firewall Pengaturan firewalld sudo yast users Kelola user dan grup sudo yast bootloader Pengaturan GRUB dan parameter kernel sudo yast services-manager Aktif/nonaktifkan service systemd sudo yast timezone Zona waktu sudo yast disk Partisi dan Btrfs (Expert Partitioner) sudo yast snapper Lihat dan bandingkan snapshot sudo yast sysconfig Editor /etc/sysconfig Cockpit sebagai alternatif Kalau kamu lebih suka antarmuka web, terutama di Leap 16:\nsudo zypper in cockpit sudo systemctl enable --now cockpit.socket sudo firewall-cmd --add-service=cockpit --permanent \u0026amp;\u0026amp; sudo firewall-cmd --reload Lalu buka https://alamat-server:9090 dan login dengan user sistem.\n","permalink":"https://rifkiaz.my.id/cheatsheet/yast/","summary":"\u003cp\u003eYaST adalah alat administrasi khas openSUSE. Kelebihannya: hampir semua modulnya bisa dibuka di terminal (ncurses), jadi tetap enak dipakai lewat SSH.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDi Leap 16, YaST tidak lagi disertakan. openSUSE beralih ke \u003cstrong\u003eCockpit\u003c/strong\u003e untuk administrasi lewat browser dan \u003cstrong\u003eAgama\u003c/strong\u003e sebagai installer. Contekan ini berlaku untuk Tumbleweed dan Leap 15.x.\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"dasar\"\u003eDasar\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esudo yast\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePusat kontrol YaST versi teks\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esudo yast2\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePusat kontrol versi GUI (kalau ada desktop)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eyast -l\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar semua modul yang terpasang\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esudo yast nama_modul\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLangsung buka modul tertentu\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eDi mode teks, pindah antar-elemen pakai \u003ccode\u003eTab\u003c/code\u003e, pilih pakai \u003ccode\u003eEnter\u003c/code\u003e atau \u003ccode\u003eSpasi\u003c/code\u003e, dan tombol fungsi seperti \u003ccode\u003eF10\u003c/code\u003e (OK) atau \u003ccode\u003eF9\u003c/code\u003e (keluar). Kalau \u003ccode\u003eF\u003c/code\u003e key ditangkap terminal, pakai \u003ccode\u003eAlt\u003c/code\u003e + huruf yang disorot.\u003c/p\u003e","title":"YaST"},{"content":"Contekan umum yang berlaku di hampir semua distro, bukan cuma openSUSE.\nFile dan direktori Perintah Fungsi ls -lah Daftar file lengkap dengan ukuran yang mudah dibaca cd - Kembali ke direktori sebelumnya cp -a sumber tujuan Salin dengan mempertahankan izin dan timestamp mv lama baru Pindah atau ganti nama rm -ri folder Hapus folder dengan konfirmasi mkdir -p a/b/c Buat direktori bertingkat find /etc -name \u0026quot;*.conf\u0026quot; -mtime -1 Cari file .conf yang berubah sehari terakhir grep -rn \u0026quot;teks\u0026quot; /etc Cari teks di dalam file secara rekursif tail -f /var/log/file.log Ikuti isi file log less file Baca file panjang (/ untuk cari, q keluar) Izin dan kepemilikan Perintah Fungsi chmod 644 file rw untuk pemilik, r untuk lainnya chmod 755 skrip.sh Bisa dieksekusi semua, hanya pemilik yang bisa ubah chmod +x skrip.sh Tambah izin eksekusi chown user:grup file Ganti pemilik chown -R user:grup folder Ganti pemilik secara rekursif sudo -i Masuk shell root id Lihat UID, GID, dan grup user Proses dan sumber daya Perintah Fungsi htop / top Monitor proses interaktif ps aux | grep nama Cari proses pgrep -a nama Cari PID beserta perintahnya kill PID / kill -9 PID Hentikan proses (halus / paksa) free -h Pemakaian RAM uptime Lama menyala dan load average lscpu Info CPU Disk Perintah Fungsi df -h Pemakaian per filesystem (di Btrfs, pakai btrfs filesystem usage /) du -sh * Ukuran tiap isi direktori du -h --max-depth=1 / | sort -h Cari direktori yang paling besar lsblk -f Daftar disk, partisi, filesystem, dan UUID mount | column -t Filesystem yang sedang di-mount Arsip dan kompresi Perintah Fungsi tar czf arsip.tar.gz folder/ Buat arsip gzip tar xzf arsip.tar.gz Ekstrak arsip gzip tar tf arsip.tar.gz Lihat isi arsip tanpa ekstrak zstd -19 file Kompres dengan zstd unzip file.zip -d tujuan Ekstrak zip ke folder tertentu Remote Perintah Fungsi ssh user@host Login SSH ssh-keygen -t ed25519 Buat SSH key baru ssh-copy-id user@host Pasang public key di server scp file user@host:/tujuan/ Salin file ke server rsync -avh --progress sumber/ user@host:/tujuan/ Sinkronisasi folder, bisa dilanjutkan kalau putus ","permalink":"https://rifkiaz.my.id/cheatsheet/perintah-dasar/","summary":"\u003cp\u003eContekan umum yang berlaku di hampir semua distro, bukan cuma openSUSE.\u003c/p\u003e\n\u003ch2 id=\"file-dan-direktori\"\u003eFile dan direktori\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003els -lah\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar file lengkap dengan ukuran yang mudah dibaca\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ecd -\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eKembali ke direktori sebelumnya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ecp -a sumber tujuan\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSalin dengan mempertahankan izin dan timestamp\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003emv lama baru\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePindah atau ganti nama\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003erm -ri folder\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHapus folder dengan konfirmasi\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003emkdir -p a/b/c\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBuat direktori bertingkat\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003efind /etc -name \u0026quot;*.conf\u0026quot; -mtime -1\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari file \u003ccode\u003e.conf\u003c/code\u003e yang berubah sehari terakhir\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003egrep -rn \u0026quot;teks\u0026quot; /etc\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari teks di dalam file secara rekursif\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etail -f /var/log/file.log\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eIkuti isi file log\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eless file\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBaca file panjang (\u003ccode\u003e/\u003c/code\u003e untuk cari, \u003ccode\u003eq\u003c/code\u003e keluar)\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"izin-dan-kepemilikan\"\u003eIzin dan kepemilikan\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003echmod 644 file\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003erw untuk pemilik, r untuk lainnya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003echmod 755 skrip.sh\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBisa dieksekusi semua, hanya pemilik yang bisa ubah\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003echmod +x skrip.sh\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eTambah izin eksekusi\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003echown user:grup file\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eGanti pemilik\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003echown -R user:grup folder\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eGanti pemilik secara rekursif\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003esudo -i\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMasuk shell root\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eid\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat UID, GID, dan grup user\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"proses-dan-sumber-daya\"\u003eProses dan sumber daya\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ehtop\u003c/code\u003e / \u003ccode\u003etop\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eMonitor proses interaktif\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eps aux | grep nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari proses\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003epgrep -a nama\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari PID beserta perintahnya\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ekill PID\u003c/code\u003e / \u003ccode\u003ekill -9 PID\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eHentikan proses (halus / paksa)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003efree -h\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePemakaian RAM\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003euptime\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLama menyala dan load average\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003elscpu\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eInfo CPU\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"disk\"\u003eDisk\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edf -h\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePemakaian per filesystem (di Btrfs, pakai \u003ccode\u003ebtrfs filesystem usage /\u003c/code\u003e)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edu -sh *\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eUkuran tiap isi direktori\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edu -h --max-depth=1 / | sort -h\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eCari direktori yang paling besar\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003elsblk -f\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eDaftar disk, partisi, filesystem, dan UUID\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003emount | column -t\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eFilesystem yang sedang di-mount\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"arsip-dan-kompresi\"\u003eArsip dan kompresi\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etar czf arsip.tar.gz folder/\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBuat arsip gzip\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etar xzf arsip.tar.gz\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eEkstrak arsip gzip\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etar tf arsip.tar.gz\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLihat isi arsip tanpa ekstrak\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ezstd -19 file\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eKompres dengan zstd\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eunzip file.zip -d tujuan\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eEkstrak zip ke folder tertentu\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"remote\"\u003eRemote\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePerintah\u003c/th\u003e\n          \u003cth\u003eFungsi\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003essh user@host\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eLogin SSH\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003essh-keygen -t ed25519\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eBuat SSH key baru\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003essh-copy-id user@host\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003ePasang public key di server\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003escp file user@host:/tujuan/\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSalin file ke server\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ersync -avh --progress sumber/ user@host:/tujuan/\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSinkronisasi folder, bisa dilanjutkan kalau putus\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e","title":"Perintah Dasar Linux"},{"content":"Laptop saya awalnya pakai GNOME, lalu saya pasang KDE Plasma di atasnya. Setelah betah di Plasma, saya mau dua hal: tampilannya dibuat mirip macOS, dan GNOME dibersihkan supaya nggak ada dua desktop yang makan tempat dan bikin bingung saat update.\nSupaya bisa diulang di mesin lain, semuanya saya bungkus jadi satu skrip: kde-macos.sh. Skrip ini sudah lolos bash -n dan shellcheck.\nIsi skrip Skrip dijalankan sebagai user biasa (bukan root) dari dalam sesi Plasma. Bagian yang butuh root memakai sudo.\nchmod +x kde-macos.sh ./kde-macos.sh all # varian gelap (default) VARIANT=light ./kde-macos.sh all # varian terang Mode all menjalankan langkah-langkah ini berurutan:\nBackup konfigurasi KDE (kdeglobals, kwinrc, layout panel, GTK, Kvantum) ke ~/kde-macos-backup/. Perintah restore-nya dicetak di terminal. SDDM dipasang dan diaktifkan menggantikan GDM. Tumbleweed sekarang punya dua mekanisme display manager: sddm.service sendiri (hasil Display Manager Rework) dan cara lama lewat update-alternatives. Skrip menangani keduanya. Aplikasi pengganti dipasang: Dolphin, Konsole, Kate, Okular, Gwenview, Ark, dan lainnya, supaya kamu nggak kehilangan file manager dan terminal setelah aplikasi GNOME dihapus. Tema WhiteSur dari vinceliuice dipasang lengkap: global theme, Plasma style, dekorasi jendela, Kvantum, tema GTK dan libadwaita, ikon, kursor, layar login SDDM, plus font Inter sebagai pengganti San Francisco. Tampilan ala macOS diterapkan: tombol jendela di kiri (tutup, minimize, maximize) efek Magic Lamp (mirip Genie) saat minimize, plus blur panel atas berisi menu aplikasi, menu global, system tray, dan jam dock melayang di tengah bawah yang menghindar saat tertutup jendela GNOME dihapus, dengan pengaman (dibahas di bawah). Tiap langkah juga bisa dijalankan sendiri:\n./kde-macos.sh backup # cadangkan konfigurasi ./kde-macos.sh dm # pasang \u0026amp; aktifkan SDDM ./kde-macos.sh apps # aplikasi KDE pengganti ./kde-macos.sh theme # unduh \u0026amp; pasang WhiteSur ./kde-macos.sh sddm-theme # pasang ulang tema login SDDM saja ./kde-macos.sh apply # terapkan tema, tombol, font, efek ./kde-macos.sh layout # susun ulang panel atas + dock ./kde-macos.sh remove-gnome # hapus GNOME Cara skrip menerapkan tema Semua diatur lewat tool bawaan Plasma 6, jadi nggak ada edit file konfigurasi yang rapuh:\nplasma-apply-lookandfeel, plasma-apply-colorscheme, plasma-apply-desktoptheme, dan plasma-apply-cursortheme untuk tema. kwriteconfig6 untuk tombol jendela (ButtonsOnLeft=XIA), efek KWin, font, dan gaya aplikasi. Panel dan dock dibuat dengan skrip JavaScript Plasma yang dikirim lewat qdbus6 ... evaluateScript. Aplikasi yang disematkan di dock hanya yang memang terpasang. WhiteSur punya banyak varian (alt, opaque, sharp, dan seterusnya). Fungsi pick_variant di skrip memilih varian paling dasar sesuai VARIANT=dark atau light.\nMenghapus GNOME dengan aman Bagian ini yang paling berisiko, jadi skripnya sengaja hati-hati:\nDitunda kalau sesi masih lewat GDM. Kalau kamu login lewat GDM, menghapus GDM di tengah jalan bisa mematikan sesi saat zypper masih bekerja. Skrip menolak dan menyuruh reboot dulu, login lewat SDDM, baru jalankan ./kde-macos.sh remove-gnome. Tanpa zypper -n. Pattern dan paket inti GNOME dihapus dengan zypper rm biasa, jadi zypper menampilkan daftar lengkap (termasuk dependensi yang ikut terhapus) dan minta konfirmasi. Baca daftarnya sebelum menjawab y. Kalau ada paket Plasma atau KDE ikut tercantum, jawab n. Aplikasi bawaan GNOME ditanyakan terpisah, jadi kamu bisa menyimpan yang masih dipakai. gnome-keyring dipertahankan, karena Chrome dan VS Code masih memakainya untuk menyimpan kredensial lewat libsecret. Setelah itu GNOME dikunci dengan zypper al, supaya nggak terpasang lagi diam-diam saat zypper dup. Daftar kuncinya bisa dilihat dengan zypper ll dan dilepas dengan sudo zypper rl \u0026lt;nama\u0026gt;. Masalah yang saya temui di run pertama Run pertama sebagian besar berhasil, tapi ada tiga hal yang gagal. Ketiganya sudah diperbaiki di skrip yang ada di atas, dan penyebabnya cukup menarik untuk dicatat.\n1. Tema SDDM gagal terpasang. Installer SDDM bawaan WhiteSur menentukan versi tema dari output plasmashell -v. Karena installer itu dijalankan dengan sudo, nggak ada display yang bisa diakses, plasmashell gagal (muncul pesan qt.qpa.xcb di log), dan nama folder sumber jadi kosong: WhiteSur-. Solusinya, skrip memasang tema SDDM sendiri: versi Plasma dibaca sebagai user biasa (dengan fallback ke rpm -q plasma6-workspace), lalu dipetakan ke folder yang sesuai. Untuk Plasma 6.2 ke atas dipakai WhiteSur-6.2.\n2. Kvantum terpasang versi terang padahal pilih gelap. Ternyata varian gelap Kvantum WhiteSur bukan folder terpisah, melainkan file WhiteSurDark.kvconfig di dalam folder WhiteSur. Skrip sekarang mencari file .kvconfig, bukan nama folder.\n3. \u0026ldquo;Font Inter tidak ditemukan\u0026rdquo; padahal sudah terpasang. Ini bug klasik bash. Pengecekan awalnya:\nfc-list | grep -qi \u0026#39;Inter\u0026#39; Dengan set -o pipefail aktif, grep -q berhenti begitu menemukan baris pertama yang cocok, fc-list yang masih menulis output kena SIGPIPE (exit 141), dan seluruh pipeline dianggap gagal. Perbaikannya: jangan pakai -q, buang output ke /dev/null, dan cocokkan nama family dengan lebih ketat:\nfc-list : family | grep -iE \u0026#39;^Inter([ ,]|$)\u0026#39; \u0026gt;/dev/null Setelah memakai versi yang sudah diperbaiki, cukup jalankan ulang bagian yang gagal:\n./kde-macos.sh sddm-theme ./kde-macos.sh apply Lalu reboot, login lewat SDDM yang sudah bertema WhiteSur, dan hapus GNOME:\n./kde-macos.sh remove-gnome Pesan yang boleh diabaikan plasma-systemmonitor dilewati: nama paketnya di Tumbleweed berbeda. Skrip sekarang juga mencoba plasma6-systemmonitor. Modul menu global untuk aplikasi GTK tidak ada di repo resmi. Efeknya cuma menu aplikasi GTK yang nggak tampil di panel atas, apalagi di Wayland. Aplikasi KDE/Qt tetap normal. Installer tema GTK sempat memasang tema GNOME Shell karena GNOME masih terdeteksi. Tema itu nggak berpengaruh ke KDE, dan akan ikut hilang bersama GNOME. Kalau mau kembali Konfigurasi lama ada di ~/kde-macos-backup/. Ekstrak dengan tar -C ~ -xzf \u0026lt;file-backup\u0026gt;, lalu logout dan login lagi.\nReferensi:\nWhiteSur KDE openSUSE SDB: Change Display Manager ","permalink":"https://rifkiaz.my.id/posts/kde-plasma-ala-macos-opensuse-tumbleweed/","summary":"\u003cp\u003eLaptop saya awalnya pakai GNOME, lalu saya pasang KDE Plasma di atasnya. Setelah betah di Plasma, saya mau dua hal: tampilannya dibuat mirip macOS, dan GNOME dibersihkan supaya nggak ada dua desktop yang makan tempat dan bikin bingung saat update.\u003c/p\u003e\n\u003cp\u003eSupaya bisa diulang di mesin lain, semuanya saya bungkus jadi satu skrip: \u003cstrong\u003e\u003ca href=\"assets/kde-macos.sh\"\u003ekde-macos.sh\u003c/a\u003e\u003c/strong\u003e. Skrip ini sudah lolos \u003ccode\u003ebash -n\u003c/code\u003e dan \u003ccode\u003eshellcheck\u003c/code\u003e.\u003c/p\u003e\n\u003ch2 id=\"isi-skrip\"\u003eIsi skrip\u003c/h2\u003e\n\u003cp\u003eSkrip dijalankan sebagai \u003cstrong\u003euser biasa\u003c/strong\u003e (bukan root) dari dalam sesi Plasma. Bagian yang butuh root memakai \u003ccode\u003esudo\u003c/code\u003e.\u003c/p\u003e","title":"KDE Plasma ala macOS di openSUSE Tumbleweed (Sekaligus Pensiunkan GNOME)"},{"content":"Kasusnya sederhana: frontend (di localhost saat development dan di domain production) perlu membaca file langsung dari bucket Google Cloud Storage. Tanpa aturan CORS, browser akan menolak response-nya dengan error klasik No 'Access-Control-Allow-Origin' header.\nAturan CORS yang mau dipasang kira-kira begini:\n[ { \u0026#34;origin\u0026#34;: [\u0026#34;http://localhost:8090/\u0026#34;, \u0026#34;https://example.com\u0026#34;], \u0026#34;method\u0026#34;: [\u0026#34;GET\u0026#34;], \u0026#34;responseHeader\u0026#34;: [\u0026#34;Content-Type\u0026#34;], \u0026#34;maxAgeSeconds\u0026#34;: 3600 } ] Sekilas benar, tapi ada satu jebakan di situ.\nJebakan: trailing slash di origin Perhatikan http://localhost:8090/. Header Origin yang dikirim browser tidak pernah diakhiri slash. Isinya selalu http://localhost:8090. GCS mencocokkan origin secara persis, jadi entri dengan slash di belakang tidak akan pernah cocok, dan request dari localhost tetap kena CORS error walaupun aturannya \u0026ldquo;sudah dipasang\u0026rdquo;.\nJadi tulis origin tanpa slash: http://localhost:8090.\nCara 1: lewat Console Buka bucket di Cloud Console, lalu edit konfigurasi CORS-nya. Isi form-nya:\nList of allowed origins: http://localhost:8090, https://example.com Specify methods: centang GET saja. Tambahkan HEAD kalau frontend juga mengecek metadata file. OPTIONS nggak perlu dicentang karena preflight sudah ditangani GCS otomatis. List of allowed response headers: Content-Type Cache expiry time: 3600 (boleh dikosongkan, default-nya juga 3600 detik) Lalu Save.\nCara 2: lewat gcloud Bisa dari terminal lokal atau Cloud Shell (ikon \u0026gt;_ di kanan atas Console):\ncat \u0026gt; cors.json \u0026lt;\u0026lt;\u0026#39;JSON\u0026#39; [ { \u0026#34;origin\u0026#34;: [\u0026#34;http://localhost:8090\u0026#34;, \u0026#34;https://example.com\u0026#34;], \u0026#34;method\u0026#34;: [\u0026#34;GET\u0026#34;], \u0026#34;responseHeader\u0026#34;: [\u0026#34;Content-Type\u0026#34;], \u0026#34;maxAgeSeconds\u0026#34;: 3600 } ] JSON gcloud storage buckets update gs://\u0026lt;nama-bucket\u0026gt; --cors-file=cors.json Kedua cara hasilnya sama, pilih salah satu saja. Kalau konfigurasinya disimpan di repo, cara gcloud lebih enak karena cors.json bisa ikut di-review.\nVerifikasi Cek konfigurasi yang aktif:\ngcloud storage buckets describe gs://\u0026lt;nama-bucket\u0026gt; --format=\u0026#34;default(cors_config)\u0026#34; Atau tes langsung seperti browser, dengan mengirim header Origin:\ncurl -sI -H \u0026#34;Origin: http://localhost:8090\u0026#34; \\ https://storage.googleapis.com/\u0026lt;nama-bucket\u0026gt;/\u0026lt;path-object\u0026gt; | grep -i access-control Kalau berhasil, akan muncul:\naccess-control-allow-origin: http://localhost:8090 Kalau baris itu nggak muncul, cek lagi ejaan origin-nya (skema http/https, port, dan trailing slash).\nCORS bukan izin akses Ini yang sering terlewat. CORS cuma mengatur apakah browser boleh membaca response lintas origin. Izin untuk mengakses objeknya sendiri diatur terpisah.\nKalau bucket memakai uniform bucket-level access dan public access prevention (setelan yang aman dan memang disarankan), request dari frontend tetap akan dapat 403 walaupun CORS sudah benar. Frontend butuh salah satu dari ini:\nSigned URL, dibuat oleh backend dengan masa berlaku pendek. Ini pilihan yang tepat untuk file yang tidak boleh diakses sembarang orang. IAM allUsers dengan role Storage Object Viewer, kalau isi bucket memang dimaksudkan publik (misalnya aset statis). Untuk ini, public access prevention harus dimatikan dulu, jadi pastikan bucket-nya memang khusus untuk file publik. Urutan debugging yang saya pakai: kalau dapat 403, masalahnya izin (IAM atau signed URL). Kalau status-nya 200 tapi browser tetap protes soal CORS, baru masalahnya ada di aturan CORS.\n","permalink":"https://rifkiaz.my.id/posts/cors-google-cloud-storage/","summary":"\u003cp\u003eKasusnya sederhana: frontend (di \u003ccode\u003elocalhost\u003c/code\u003e saat development dan di domain production) perlu membaca file langsung dari bucket Google Cloud Storage. Tanpa aturan CORS, browser akan menolak response-nya dengan error klasik \u003ccode\u003eNo 'Access-Control-Allow-Origin' header\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eAturan CORS yang mau dipasang kira-kira begini:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;origin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:8090/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;method\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;responseHeader\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;maxAgeSeconds\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3600\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSekilas benar, tapi ada satu jebakan di situ.\u003c/p\u003e\n\u003ch2 id=\"jebakan-trailing-slash-di-origin\"\u003eJebakan: trailing slash di origin\u003c/h2\u003e\n\u003cp\u003ePerhatikan \u003ccode\u003ehttp://localhost:8090/\u003c/code\u003e. Header \u003ccode\u003eOrigin\u003c/code\u003e yang dikirim browser \u003cstrong\u003etidak pernah\u003c/strong\u003e diakhiri slash. Isinya selalu \u003ccode\u003ehttp://localhost:8090\u003c/code\u003e. GCS mencocokkan origin secara persis, jadi entri dengan slash di belakang tidak akan pernah cocok, dan request dari localhost tetap kena CORS error walaupun aturannya \u0026ldquo;sudah dipasang\u0026rdquo;.\u003c/p\u003e","title":"Mengatur CORS di Bucket Google Cloud Storage"},{"content":"Di tim, alert kami tersebar di mana-mana: error aplikasi di Sentry, APM di Elastic, sebagian service di New Relic, dan deploy frontend di Vercel. Masing-masing punya integrasi Discord sendiri, tapi ada yang berbayar, ada yang terkunci lisensi, dan tampilannya beda-beda semua.\nAkhirnya saya pakai satu pola untuk semuanya:\nSumber alert → Webhook n8n → Code (format embed) → HTTP Request → Discord n8n jadi \u0026ldquo;pintu\u0026rdquo; tunggal. Semua pesan dirapikan di node Code jadi embed card Discord dengan format yang konsisten: apa yang terjadi, di mana, kemungkinan penyebabnya, dan link untuk investigasi. Tulisan ini merangkum setup-nya, termasuk jebakan-jebakan yang saya temui.\nSemua URL, nama service, dan endpoint di tulisan ini contoh. Ganti dengan punya kamu.\nPola dasar di n8n Setiap sumber pakai tiga node:\nWebhook (POST) untuk menerima payload. Code (JavaScript, mode Run Once for All Items) untuk mengubah payload jadi { username, embeds }. HTTP Request untuk mengirim ke webhook Discord. Setting HTTP Request-nya selalu sama:\nSetting Nilai Method POST URL URL webhook Discord (Edit Channel → Integrations → Webhooks) Send Body ON, tipe JSON Specify Body Using JSON JSON {{ JSON.stringify($json) }} (mode Expression) Kenapa nggak pakai node Discord bawaan n8n? Karena dengan HTTP Request kamu punya kontrol penuh atas bentuk embed.\nBeberapa hal yang sering bikin bingung di awal:\nKode JavaScript harus di node Code, bukan di kolom JSON HTTP Request. Kolom JSON cuma menerima JSON atau ekspresi {{ }}. URL test vs production. /webhook-test/... cuma aktif waktu kamu menekan Listen for test event. Untuk dipakai sungguhan, Publish workflow dan pakai /webhook/.... URL webhook Discord itu rahasia. Siapa pun yang memegangnya bisa kirim pesan ke channel kamu. Hati-hati waktu berbagi screenshot. Batas Discord: title 256 karakter, description 4096, value field 1024, maksimal 10 embed per pesan. String kosong dan URL relatif ditolak dengan error 400. Karena itu, semua node Code di bawah memakai tiga helper yang sama:\n// potong sesuai limit Discord; undefined kalau kosong (Discord menolak string kosong) const trunc = (s, n) =\u0026gt; { s = String(s ?? \u0026#39;\u0026#39;).trim(); if (!s) return undefined; return s.length \u0026gt; n ? s.slice(0, n - 1) + \u0026#39;…\u0026#39; : s; }; // Discord hanya menerima URL absolut const safeUrl = (u) =\u0026gt; (typeof u === \u0026#39;string\u0026#39; \u0026amp;\u0026amp; /^https?:\\/\\//.test(u) ? u : undefined); // normalisasi timestamp ke ISO standar const safeTs = (t) =\u0026gt; { const d = new Date(Number(t) || t); return isNaN(d) ? new Date().toISOString() : d.toISOString(); }; Satu workflow boleh punya beberapa Webhook trigger. Saya taruh jalur Sentry dan Grafana di satu canvas dengan path berbeda, jadi kalau satu jalur perlu di-debug, jalur lain nggak tersentuh. Alternatifnya satu Webhook plus node Switch yang memilah berdasarkan header atau isi body, tapi menurut saya dua trigger lebih jelas.\n1. Sentry → Discord Payload webhook Sentry itu besar (bisa ratusan KB, kebanyakan stacktrace dan debug_meta). Kamu cukup ambil beberapa field penting.\nconst body = $input.first().json.body; const ev = body?.data?.event ?? {}; // tags di Sentry berbentuk array of [key, value] const tags = Object.fromEntries(ev.tags ?? []); const exc = ev.exception?.values?.[0] ?? {}; const frames = exc.stacktrace?.frames ?? []; // frame in_app terakhir = lokasi kode kita yang paling dekat dengan crash const appFrame = [...frames].reverse().find(f =\u0026gt; f.in_app \u0026amp;\u0026amp; f.filename); const project = ev.url?.match(/\\/projects\\/[^/]+\\/([^/]+)\\//)?.[1] ?? \u0026#39;-\u0026#39;; const trunc = (s, n) =\u0026gt; { if (s === null || s === undefined || s === \u0026#39;\u0026#39;) return \u0026#39;-\u0026#39;; s = String(s); return s.length \u0026gt; n ? s.slice(0, n - 1) + \u0026#39;…\u0026#39; : s; }; const levelColor = { fatal: 0x8B0000, error: 0xE03E2F, warning: 0xF5A623, info: 0x3498DB, debug: 0x95A5A6, }; const level = ev.level ?? tags.level ?? \u0026#39;error\u0026#39;; const isTest = tags.sample_event === \u0026#39;yes\u0026#39; || !!ev.occurrence; const errType = ev.metadata?.type ?? exc.type; const errValue = ev.metadata?.value ?? exc.value; let title = errType ? `${errType}: ${errValue ?? \u0026#39;\u0026#39;}` : (ev.message || ev.title || \u0026#39;Sentry Issue\u0026#39;); if (isTest) title = `[TEST] ${title}`; const location = appFrame ? `\\`${appFrame.filename}:${appFrame.lineno ?? \u0026#39;?\u0026#39;}\\`\\n${appFrame.function ?? \u0026#39;\u0026#39;}` : (ev.culprit ?? \u0026#39;-\u0026#39;); const embed = { title: trunc(title, 250), url: ev.web_url, color: levelColor[level] ?? levelColor.error, description: trunc(ev.culprit ? `**Culprit:** \\`${ev.culprit}\\`` : \u0026#39;\u0026#39;, 4000), fields: [ { name: \u0026#39;📦 Project\u0026#39;, value: trunc(project, 1000), inline: true }, { name: \u0026#39;🌍 Environment\u0026#39;, value: trunc(ev.environment, 1000), inline: true }, { name: \u0026#39;🚨 Level\u0026#39;, value: level.toUpperCase(), inline: true }, { name: \u0026#39;🏷️ Release\u0026#39;, value: trunc(ev.release, 1000), inline: true }, { name: \u0026#39;📱 Device\u0026#39;, value: trunc(ev.contexts?.device?.model ?? tags.device, 1000), inline: true }, { name: \u0026#39;💻 OS\u0026#39;, value: trunc(ev.contexts?.os?.os ?? tags.os, 1000), inline: true }, { name: \u0026#39;📍 Location\u0026#39;, value: trunc(location, 1000), inline: false }, ], footer: { text: `Rule: ${body?.data?.triggered_rule ?? \u0026#39;-\u0026#39;} • Issue #${ev.issue_id ?? \u0026#39;-\u0026#39;}` }, timestamp: ev.datetime ?? new Date().toISOString(), }; // @here hanya untuk error production yang bukan test const mention = (ev.environment === \u0026#39;production\u0026#39; \u0026amp;\u0026amp; [\u0026#39;error\u0026#39;, \u0026#39;fatal\u0026#39;].includes(level) \u0026amp;\u0026amp; !isTest) ? \u0026#39;@here\u0026#39; : \u0026#39;\u0026#39;; return [{ json: { username: \u0026#39;Sentry\u0026#39;, content: mention, embeds: [embed], allowed_mentions: { parse: [\u0026#39;everyone\u0026#39;] }, }, }]; Beberapa catatan:\nTest notification dari Sentry itu sample event (tag sample_event: yes), jadi datanya dummy. Kode di atas memberi prefix [TEST] supaya nggak bikin panik. Saya sengaja tidak menampilkan data user (email, id) di Discord. Channel Discord biasanya lebih luas aksesnya daripada Sentry. Tambahkan node IF sebelum Code dengan kondisi {{ $json.body.action }} sama dengan triggered, supaya event lain nggak ikut terkirim. Untuk keamanan, verifikasi header sentry-hook-signature (HMAC-SHA256 dari body memakai Client Secret integrasi Sentry) supaya orang lain nggak bisa spam webhook kamu. 2. Elasticsearch APM → Discord (lewat Grafana) Ini bagian yang paling banyak jebakannya.\nKenapa nggak langsung dari Kibana? Rencana awalnya Kibana Alerting dengan connector Webhook ke n8n. Ternyata di lisensi Basic (gratis), connector yang tersedia cuma Index dan Server log. Webhook butuh lisensi Gold ke atas. Tombol Start trial juga bukan solusi, karena cuma 30 hari dan nggak bisa diulang.\nPilihan yang tersisa:\nn8n polling ke Elasticsearch ElastAlert2 Grafana Alerting Infra tambahan Tidak ada Satu service lagi Tidak ada kalau sudah punya Grafana Dedup / grouping / resolved Atur sendiri Built-in Built-in Biaya Gratis Gratis Gratis (OSS) Karena kami sudah punya Grafana untuk Prometheus, jalan paling ringan adalah menambahkan Elasticsearch sebagai data source di Grafana dan membuat alert di sana. Data nggak perlu diduplikasi, config OTel Collector nggak berubah, dan Kibana APM tetap jadi tempat investigasi.\nSempat juga terpikir pindah ke LGTM (Loki, Grafana, Tempo, Mimir), atau kirim paralel lewat collector chaining. Itu bisa dan cukup umum, tapi kalau kebutuhan APM sudah terpenuhi oleh Elastic, yang sebenarnya kurang cuma alerting. Jadi nggak perlu bangun stack baru hanya untuk itu.\nCari nama field status code Dokumen APM yang punya status HTTP adalah transaction, bukan span. Di Grafana Explore, cek field mana yang terisi:\nprocessor.event:transaction AND service.environment:PROD AND _exists_:http.response.status_code Kalau kosong, coba labels.http_response_status_code. Itu terjadi kalau APM Server versi lama belum mengenali nama atribut OTel baru, sehingga atributnya masuk ke labels.*.\nLalu lihat volume 4xx dan 5xx dalam 30 hari untuk menentukan threshold. Di kasus saya, 5xx jarang (kurang dari satu per hari), sementara 4xx didominasi 401 dari token expired. Kalau 401 digabung ke rule 4xx biasa, alert bakal bunyi terus.\nTiga rule Rule Query tambahan Threshold / 5 menit Pending Severity HTTP 5xx - PROD http.response.status_code:[500 TO 599] \u0026gt; 0 None critical HTTP 4xx - PROD http.response.status_code:[400 TO 499] AND NOT http.response.status_code:401 \u0026gt; 3 2m warning HTTP 401 spike - PROD http.response.status_code:401 \u0026gt; 20 1m warning Semua query diawali processor.event:transaction AND service.environment:PROD AND .... Rule 401 spike bukan untuk menangkap token expired biasa, tapi untuk mendeteksi masalah autentikasi massal, misalnya auth service down atau JWT secret berubah.\nSetting rule 5xx di Grafana:\nQuery A (Elasticsearch, Query type Metrics), time range now-5m, metric Count. Group by, urutannya penting: Terms service.name → Terms transaction.name → Terms http.response.status_code (semua Order by Doc Count) → Date Histogram @timestamp dengan interval 1m. Expression B: Reduce, Sum, mode Drop Non-numeric Values (mode Strict bisa menghasilkan NaN). Expression C: Threshold, input B, IS ABOVE 0, jadi alert condition. No data handling: Normal. Ini wajib. Tanpa ini, setiap periode tanpa error akan mengirim alert DatasourceNoData. Labels: severity=critical, env=PROD, team=backend. Custom annotation count: {{ humanize $values.B.Value }}, plus apm_transaction_url dan apm_errors_url yang mengarah ke halaman APM di Kibana: https://kibana.example.com/app/apm/services/{{ index $labels \u0026#34;service.name\u0026#34; }}/transactions/view?transactionName={{ urlquery (index $labels \u0026#34;transaction.name\u0026#34;) }}\u0026amp;transactionType=request\u0026amp;environment=PROD\u0026amp;rangeFrom=now-30m\u0026amp;rangeTo=now https://kibana.example.com/app/apm/services/{{ index $labels \u0026#34;service.name\u0026#34; }}/errors?environment=PROD\u0026amp;rangeFrom=now-30m\u0026amp;rangeTo=now Untuk rule 401 spike, hapus Terms transaction.name supaya lonjakan yang tersebar di banyak endpoint tetap terhitung per service.\nGrouping notifikasi: group by alertname, service.name, transaction.name, http.response.status_code, dengan Group wait 30s, Group interval 5m, Repeat interval 1h. Hasilnya satu card per kombinasi service + endpoint + status, diulang paling cepat tiap jam, plus card resolved saat error berhenti.\nJebakan: \u0026ldquo;too many buckets\u0026rdquo; Untuk mengetes rule dengan data asli, trik yang saya pakai adalah mengubah time range sementara ke now-30d. Hasilnya malah error:\nTrying to create too many buckets. Must be less than or equal to: [65536] Penyebabnya Date Histogram dengan interval auto. Untuk rentang 30 hari, interval auto dihitung sekitar 1 menit, jadi ada 43.200 bucket per kombinasi service × endpoint × status. Dua kombinasi saja sudah lewat batas.\nSolusinya, set interval eksplisit:\nSetting Tes Produksi Time range now-30d now-5m Date Histogram interval 1d 1m Karena reduce-nya Sum, besar kecilnya interval nggak mengubah hasil hitungan. Dan jangan lupa kembalikan ke setting produksi setelah tes, kalau nggak alert lama akan dikirim ulang tiap jam selama sebulan.\nDari template teks ke embed card Integration Discord bawaan Grafana bisa dikustom pakai notification template (Go template), tapi hasilnya tetap teks biasa. Saya sempat beberapa kali iterasi template dan hasilnya selalu terasa berantakan. Akhirnya contact point saya ganti ke Webhook → n8n supaya bisa jadi embed card seperti Sentry.\nNode Code untuk payload Grafana:\nconst p = $input.first().json.body; const CODE_NAME = { 400: \u0026#39;Bad Request\u0026#39;, 401: \u0026#39;Unauthorized\u0026#39;, 403: \u0026#39;Forbidden\u0026#39;, 404: \u0026#39;Not Found\u0026#39;, 409: \u0026#39;Conflict\u0026#39;, 422: \u0026#39;Unprocessable Entity\u0026#39;, 429: \u0026#39;Too Many Requests\u0026#39;, 500: \u0026#39;Internal Server Error\u0026#39;, 502: \u0026#39;Bad Gateway\u0026#39;, 503: \u0026#39;Service Unavailable\u0026#39;, 504: \u0026#39;Gateway Timeout\u0026#39;, }; const HINT = { 400: \u0026#39;Validasi request gagal, cek payload dari client\u0026#39;, 401: \u0026#39;Token invalid/expired; kalau massal, cek auth service\u0026#39;, 403: \u0026#39;User tidak punya akses ke resource\u0026#39;, 404: \u0026#39;Resource/URL tidak ditemukan\u0026#39;, 409: \u0026#39;Konflik data/status (duplikat atau status sudah berubah)\u0026#39;, 422: \u0026#39;Validasi bisnis gagal\u0026#39;, 429: \u0026#39;Kena rate limit\u0026#39;, 500: \u0026#39;Bug/exception di aplikasi, cek stack trace di Errors APM\u0026#39;, 502: \u0026#39;Upstream/dependency membalas tidak valid\u0026#39;, 503: \u0026#39;Service/dependency down atau overload\u0026#39;, 504: \u0026#39;Timeout ke dependency (DB/Redis/API)\u0026#39;, }; const COLOR = { critical: 0xE03E2F, warning: 0xF5A623, resolved: 0x2ECC71, system: 0x95A5A6 }; const trunc = (s, n) =\u0026gt; { s = String(s ?? \u0026#39;\u0026#39;).trim(); if (!s) return undefined; return s.length \u0026gt; n ? s.slice(0, n - 1) + \u0026#39;…\u0026#39; : s; }; const safeUrl = (u) =\u0026gt; (typeof u === \u0026#39;string\u0026#39; \u0026amp;\u0026amp; /^https?:\\/\\//.test(u) ? u : undefined); const safeTs = (t) =\u0026gt; { const d = new Date(t); return isNaN(d) ? new Date().toISOString() : d.toISOString(); }; const embeds = (p?.alerts ?? []).map(a =\u0026gt; { const L = a.labels ?? {}; const A = a.annotations ?? {}; const resolved = a.status === \u0026#39;resolved\u0026#39;; const ts = safeTs(resolved \u0026amp;\u0026amp; a.endsAt \u0026amp;\u0026amp; !a.endsAt.startsWith(\u0026#39;0001\u0026#39;) ? a.endsAt : a.startsAt); const svc = L[\u0026#39;service.name\u0026#39;]; // alert sistem (DatasourceNoData / DatasourceError / TestAlert) if (!svc) { return { title: trunc(`⚠️ ${L.alertname ?? \u0026#39;Grafana alert\u0026#39;}${resolved ? \u0026#39; · resolved\u0026#39; : \u0026#39;\u0026#39;}`, 250), url: safeUrl(a.generatorURL), color: resolved ? COLOR.resolved : COLOR.system, description: trunc([ L.rulename \u0026amp;\u0026amp; `Rule: **${L.rulename}**`, A.summary, A.Error \u0026amp;\u0026amp; \u0026#39;```\u0026#39; + A.Error + \u0026#39;```\u0026#39;, ].filter(Boolean).join(\u0026#39;\\n\u0026#39;), 4000), footer: { text: \u0026#39;Grafana Alerting\u0026#39; }, timestamp: ts, }; } const code = String(L[\u0026#39;http.response.status_code\u0026#39;] ?? \u0026#39;\u0026#39;); const name = CODE_NAME[code] ?? \u0026#39;HTTP Error\u0026#39;; const endpoint = L[\u0026#39;transaction.name\u0026#39;]; const sev = L.severity ?? \u0026#39;warning\u0026#39;; const count = A.count ?? a.values?.B ?? \u0026#39;-\u0026#39;; const where = `**${svc}**${endpoint ? `\\n\\`${endpoint}\\`` : \u0026#39;\u0026#39;}`; if (resolved) { return { title: trunc(`✅ Resolved · ${code} ${name}`, 250), description: trunc(where, 4000), color: COLOR.resolved, footer: { text: trunc(`${L.alertname ?? \u0026#39;Grafana\u0026#39;} · Grafana`, 2000) }, timestamp: ts, }; } const trace = safeUrl(A.apm_transaction_url); const error = safeUrl(A.apm_errors_url); const links = [trace \u0026amp;\u0026amp; `[Trace](${trace})`, error \u0026amp;\u0026amp; `[Error](${error})`].filter(Boolean).join(\u0026#39; · \u0026#39;); const fields = [ { name: \u0026#39;Jumlah\u0026#39;, value: `${count}x / 5 menit`, inline: true }, { name: \u0026#39;Env\u0026#39;, value: trunc(L.env, 100) ?? \u0026#39;-\u0026#39;, inline: true }, { name: \u0026#39;Severity\u0026#39;, value: sev, inline: true }, { name: \u0026#39;💡 Kemungkinan penyebab\u0026#39;, value: HINT[code] ?? \u0026#39;Cek detail transaksi di APM\u0026#39;, inline: false }, ]; if (links) fields.push({ name: \u0026#39;🔗 Link\u0026#39;, value: links, inline: false }); return { title: trunc(`${sev === \u0026#39;critical\u0026#39; ? \u0026#39;🔴\u0026#39; : \u0026#39;🟠\u0026#39;} ${code} ${name}`, 250), url: trace, description: trunc(where, 4000), color: COLOR[sev] ?? COLOR.warning, fields, footer: { text: trunc(`${L.alertname ?? \u0026#39;Grafana\u0026#39;} · Grafana`, 2000) }, timestamp: ts, }; }); // Discord maksimal 10 embed per pesan const out = []; for (let i = 0; i \u0026lt; embeds.length; i += 10) { out.push({ json: { username: \u0026#39;Grafana Alert\u0026#39;, embeds: embeds.slice(i, i + 10) } }); } if (!out.length) { out.push({ json: { username: \u0026#39;Grafana Alert\u0026#39;, content: \u0026#39;⚠️ Grafana mengirim notifikasi tanpa alert.\u0026#39; } }); } return out; Hasilnya kira-kira begini:\n┃ 🔴 504 Gateway Timeout ← judul bisa diklik ke APM ┃ orders-service ┃ /v1/orders/:id ┃ ┃ Jumlah Env Severity ┃ 6x / 5 menit PROD critical ┃ ┃ 💡 Kemungkinan penyebab ┃ Timeout ke dependency (DB/Redis/API) ┃ 🔗 Trace · Error ┃ HTTP 5xx - PROD · Grafana Dua jebakan di bagian ini:\nError 400 {\u0026quot;embeds\u0026quot;: [\u0026quot;0\u0026quot;]} dari Discord. Tombol Test di contact point Grafana mengirim generatorURL relatif (?orgId=1), dan Discord menolak URL yang bukan absolut. Makanya ada safeUrl. Kalau link dari alert asli juga relatif, set root_url Grafana (GF_SERVER_ROOT_URL). Tombol Test (Predefined) nggak punya label service.name, jadi yang muncul selalu card \u0026ldquo;alert sistem\u0026rdquo; abu-abu. Pakai Test → Custom dengan label dan annotation buatan untuk melihat card lengkap. Pause vs Silence Kalau satu rule terlalu berisik (misalnya 400 dari validasi client), ada dua pilihan:\nPause evaluation Silence Rule tetap dievaluasi Tidak Ya Status alert tetap terlihat di Grafana Tidak Ya, tapi tidak dikirim Cocok untuk Berhenti sampai waktu yang belum ditentukan Menahan notifikasi selama durasi tertentu Kalau cuma kode 400 yang mau dimatikan, ubah query jadi ... AND NOT http.response.status_code:(400 OR 401).\n3. New Relic → Discord Polanya sama: NRQL Condition → Alert Policy → Workflow → Destination (Webhook) → n8n. Alerting sudah termasuk di plan New Relic tanpa biaya tambahan.\nJebakan: http.statusCode bertipe string Query WHERE http.statusCode \u0026gt;= 500 saya hasilnya kosong, padahal datanya ada. Ternyata atribut itu tersimpan sebagai string di sebagian event, dan perbandingan \u0026gt;= hanya berlaku untuk numerik. Di tampilan FACET hal ini nggak kelihatan karena string dan angka ditampilkan sama. Solusinya bungkus dengan numeric():\nSELECT count(*) FROM Transaction WHERE appName LIKE \u0026#39;Prod-%\u0026#39; AND numeric(http.statusCode) \u0026gt;= 500 FACET appName, name, http.statusCode Cek tipe atribut dengan SELECT keyset() FROM Transaction ...; atributnya akan masuk ke stringKeys atau numericKeys.\nWaktu breakdown, 4xx di New Relic ternyata hampir semuanya noise (health check yang memanggil path salah, polling dengan token invalid), jadi untuk New Relic saya cukup pantau 5xx saja. Filter appName juga wajib, karena app dev punya puluhan ribu 4xx per hari.\nSetting condition Bagian Nilai Window duration 1 minute Streaming method Event timer, 1 minute (cocok untuk data jarang) Fill data gaps with Custom static value: 0 Threshold Critical, above 0, at least once in 1 minute Signal loss Jangan buka incident saat signal hilang Fill data gaps = 0 itu penting. Tanpa itu, saat nggak ada 5xx sama sekali New Relic nggak punya data untuk dievaluasi, incident nggak pernah tertutup, dan card resolved nggak pernah terkirim.\nAlert Policy pakai One issue per condition and signal, supaya setiap kombinasi app, endpoint, dan status jadi alert sendiri.\nPayload template Di Workflow, ganti payload template default dengan ini, lalu centang notify on Activated dan Closed:\n{ \u0026#34;source\u0026#34;: \u0026#34;newrelic\u0026#34;, \u0026#34;issueUrl\u0026#34;: {{json issuePageUrl}}, \u0026#34;title\u0026#34;: {{json annotations.title.[0]}}, \u0026#34;state\u0026#34;: {{json state}}, \u0026#34;priority\u0026#34;: {{json priority}}, \u0026#34;createdAt\u0026#34;: {{json createdAt}}, \u0026#34;updatedAt\u0026#34;: {{json updatedAt}}, \u0026#34;totalIncidents\u0026#34;: {{json totalIncidents}}, \u0026#34;policyName\u0026#34;: {{json accumulations.policyName.[0]}}, \u0026#34;conditionName\u0026#34;: {{json accumulations.conditionName.[0]}}, \u0026#34;entityNames\u0026#34;: {{json entitiesData.names}}, \u0026#34;accumulations\u0026#34;: { \u0026#34;rawTag\u0026#34;: {{json accumulations.rawTag}}, \u0026#34;deepLinkUrl\u0026#34;: {{json accumulations.deepLinkUrl}} } } Nilai FACET (app, nama transaksi, status code) ada di accumulations.rawTag, dengan nama atribut sebagai key. Saran saya, tangkap dulu payload asli sebelum menulis kode. Paling gampang: Publish workflow n8n, arahkan Destination ke URL production, lalu lihat tab Executions di n8n. Cara ini menangkap payload Activated dan Closed sekaligus.\nNode Code const b = $input.first().json.body ?? {}; const acc = b.accumulations ?? {}; const raw = acc.rawTag ?? {}; const first = (v) =\u0026gt; (Array.isArray(v) ? v[0] : v); const CODE_NAME = { 500: \u0026#39;Internal Server Error\u0026#39;, 501: \u0026#39;Not Implemented\u0026#39;, 502: \u0026#39;Bad Gateway\u0026#39;, 503: \u0026#39;Service Unavailable\u0026#39;, 504: \u0026#39;Gateway Timeout\u0026#39;, }; const HINT = { 500: \u0026#39;Bug/exception di aplikasi, cek error trace di New Relic\u0026#39;, 501: \u0026#39;Endpoint/method belum diimplementasikan\u0026#39;, 502: \u0026#39;Upstream/dependency membalas tidak valid\u0026#39;, 503: \u0026#39;Service/dependency down atau overload\u0026#39;, 504: \u0026#39;Timeout ke dependency (DB/Redis/API pihak ketiga)\u0026#39;, }; const COLOR = { CRITICAL: 0xE03E2F, HIGH: 0xF5A623, MEDIUM: 0xF5A623, LOW: 0x3498DB, resolved: 0x2ECC71 }; const trunc = (s, n) =\u0026gt; { s = String(s ?? \u0026#39;\u0026#39;).trim(); if (!s) return undefined; return s.length \u0026gt; n ? s.slice(0, n - 1) + \u0026#39;…\u0026#39; : s; }; const safeUrl = (u) =\u0026gt; (typeof u === \u0026#39;string\u0026#39; \u0026amp;\u0026amp; /^https?:\\/\\//.test(u) ? u : undefined); const safeTs = (t) =\u0026gt; { const d = new Date(Number(t) || t); return isNaN(d) ? new Date().toISOString() : d.toISOString(); }; const app = first(raw.appName) ?? first(b.entityNames) ?? \u0026#39;-\u0026#39;; const txn = first(raw.name); const code = String(first(raw[\u0026#39;http.statusCode\u0026#39;]) ?? \u0026#39;\u0026#39;); // \u0026#34;WebTransaction/Expressjs/POST//api/v1/x\u0026#34; -\u0026gt; \u0026#34;POST /api/v1/x\u0026#34; const endpoint = txn ? String(txn).replace(/^WebTransaction\\/[^/]+\\//, \u0026#39;\u0026#39;).replace(/^([A-Z]+)\\/\\//, \u0026#39;$1 /\u0026#39;) : undefined; const closed = b.state === \u0026#39;CLOSED\u0026#39;; const priority = b.priority ?? \u0026#39;CRITICAL\u0026#39;; const label = code ? `${code} ${CODE_NAME[code] ?? \u0026#39;HTTP Error\u0026#39;}` : (b.conditionName ?? b.title ?? \u0026#39;New Relic alert\u0026#39;); const where = `**${app}**${endpoint ? `\\n\\`${endpoint}\\`` : \u0026#39;\u0026#39;}`; const footer = { text: trunc(`${b.policyName ?? \u0026#39;New Relic\u0026#39;} · New Relic`, 2000) }; const issue = safeUrl(b.issueUrl); const query = safeUrl(first(acc.deepLinkUrl)); let links = issue ? `[Issue](${issue})` : \u0026#39;\u0026#39;; if (query \u0026amp;\u0026amp; (links.length + query.length + 15) \u0026lt;= 1000) { links += `${links ? \u0026#39; · \u0026#39; : \u0026#39;\u0026#39;}[Query](${query})`; } let embed; if (closed) { const mins = b.createdAt \u0026amp;\u0026amp; b.updatedAt ? Math.max(1, Math.round((Number(b.updatedAt) - Number(b.createdAt)) / 60000)) : null; embed = { title: trunc(`✅ Resolved · ${label}`, 250), url: issue, description: trunc(`${where}${mins ? `\\nDurasi: ${mins} menit` : \u0026#39;\u0026#39;}`, 4000), color: COLOR.resolved, footer, timestamp: safeTs(b.updatedAt), }; } else { const fields = [ { name: \u0026#39;Priority\u0026#39;, value: priority, inline: true }, { name: \u0026#39;Incident\u0026#39;, value: String(b.totalIncidents ?? 1), inline: true }, { name: \u0026#39;Condition\u0026#39;, value: trunc(b.conditionName, 100) ?? \u0026#39;-\u0026#39;, inline: true }, ]; if (code) fields.push({ name: \u0026#39;💡 Kemungkinan penyebab\u0026#39;, value: HINT[code] ?? \u0026#39;Cek detail error di New Relic\u0026#39;, inline: false }); if (links) fields.push({ name: \u0026#39;🔗 Link\u0026#39;, value: links, inline: false }); embed = { title: trunc(`${priority === \u0026#39;CRITICAL\u0026#39; ? \u0026#39;🔴\u0026#39; : \u0026#39;🟠\u0026#39;} ${label}`, 250), url: issue, description: trunc(where, 4000), color: COLOR[priority] ?? COLOR.CRITICAL, fields, footer, timestamp: safeTs(b.createdAt), }; } return [{ json: { username: \u0026#39;New Relic Alert\u0026#39;, embeds: [embed] } }]; Payload New Relic nggak menyertakan jumlah error, jadi card menampilkan jumlah incident dan, saat resolved, durasinya.\nCara tes tanpa menunggu error asli Pin data palsu di node Webhook. Di panel Output node Webhook, klik ikon pensil, isi payload tiruan (misalnya state: \u0026quot;ACTIVATED\u0026quot; dengan rawTag berisi app, nama transaksi, dan \u0026quot;http.statusCode\u0026quot;: [\u0026quot;500\u0026quot;]), lalu Execute node Code dan HTTP Request. Ubah ke CLOSED untuk melihat card resolved. Jangan lupa Unpin setelahnya, kalau nggak workflow akan terus memakai data palsu. End-to-end dengan condition sementara, misalnya query 400 dari satu app, tunggu card masuk, lalu hapus condition-nya dan tunggu card resolved. Kalau output node Code ada field myNewField: 1, berarti yang jalan masih kode contoh bawaan n8n. Kalau muncul Unexpected token '}', kemungkinan kodenya terpotong waktu di-paste; pastikan baris terakhir editor adalah return ....\n4. Notifikasi deploy Vercel → Discord Webhook akun Vercel cuma tersedia di plan Pro dan Enterprise. Jalan gratisnya lewat GitHub: kalau project Vercel terhubung ke repo GitHub, Vercel selalu melaporkan status deploy ke GitHub sebagai deployment status, dan webhook GitHub itu gratis.\nWebhook GitHub Di Settings → Webhooks repo (atau di level organisasi supaya semua repo ikut):\nPayload URL: https://n8n.example.com/webhook/github-vercel Content type: application/json (default-nya form-urlencoded, ini sering kelewat) Events: Let me select individual events, uncheck Pushes, centang Deployment statuses saja Workflow n8n Saya juga ingin card menampilkan siapa yang commit dan judul commit-nya. Masalahnya, di event deployment_status pembuat deploy tercatat sebagai vercel[bot]. Jadi perlu satu panggilan ke GitHub API:\nUrutan node-nya seperti di diagram di atas: Filter Deploy → GitHub Commit → Format Card → HTTP Request ke Discord.\nCode Filter Deploy (namanya harus persis, karena dibaca node berikutnya):\n// set true kalau hanya ingin notifikasi deploy Production (tanpa Preview) const ONLY_PRODUCTION = false; const h = $input.first().json.headers ?? {}; const b = $input.first().json.body ?? {}; // abaikan event selain deployment_status (termasuk \u0026#34;ping\u0026#34;) if (h[\u0026#39;x-github-event\u0026#39;] !== \u0026#39;deployment_status\u0026#39;) return []; const ds = b.deployment_status ?? {}; const dep = b.deployment ?? {}; // hanya deploy dari Vercel if (ds.creator?.login !== \u0026#39;vercel[bot]\u0026#39;) return []; // hanya saat deploy selesai if (![\u0026#39;success\u0026#39;, \u0026#39;failure\u0026#39;, \u0026#39;error\u0026#39;].includes(ds.state)) return []; // \u0026#34;Production – nama-project\u0026#34; / \u0026#34;Preview – nama-project\u0026#34; const envRaw = ds.environment ?? dep.environment ?? \u0026#39;\u0026#39;; const [envName, projectFromEnv] = envRaw.split(/\\s+[–-]\\s+/); const isProd = /production/i.test(envName ?? \u0026#39;\u0026#39;); if (ONLY_PRODUCTION \u0026amp;\u0026amp; !isProd) return []; const safeUrl = (u) =\u0026gt; (typeof u === \u0026#39;string\u0026#39; \u0026amp;\u0026amp; /^https?:\\/\\//.test(u) ? u : undefined); const repo = b.repository?.full_name ?? \u0026#39;\u0026#39;; return [{ json: { ok: ds.state === \u0026#39;success\u0026#39;, isProd, project: projectFromEnv || b.repository?.name || repo, repo, sha: dep.sha ?? \u0026#39;\u0026#39;, ref: dep.ref ?? \u0026#39;-\u0026#39;, siteUrl: safeUrl(ds.environment_url), logUrl: safeUrl(ds.target_url), createdAt: ds.created_at, }, }]; HTTP Request GitHub Commit:\nMethod GET, URL https://api.github.com/repos/{{ $json.repo }}/commits/{{ $json.sha }} Auth: Header Auth Authorization: Bearer \u0026lt;token\u0026gt;, pakai fine-grained token dengan permission Contents: Read-only saja Header Accept: application/vnd.github+json dan User-Agent bebas Settings → On Error: Continue, supaya kalau token kedaluwarsa atau kena rate limit, notifikasi tetap terkirim tanpa nama author Code Format Card:\nconst d = $(\u0026#39;Filter Deploy\u0026#39;).first().json; const c = $input.first().json ?? {}; const trunc = (s, n) =\u0026gt; { s = String(s ?? \u0026#39;\u0026#39;).trim(); if (!s) return undefined; return s.length \u0026gt; n ? s.slice(0, n - 1) + \u0026#39;…\u0026#39; : s; }; const login = c.author?.login; // username GitHub const name = c.commit?.author?.name; // nama di git config const avatar = c.author?.avatar_url; const message = (c.commit?.message ?? \u0026#39;\u0026#39;).split(\u0026#39;\\n\u0026#39;)[0]; // judul commit const shortSha = d.sha.slice(0, 7); const commitUrl = d.sha \u0026amp;\u0026amp; d.repo ? `https://github.com/${d.repo}/commit/${d.sha}` : undefined; const links = [ d.siteUrl \u0026amp;\u0026amp; `[Buka website](${d.siteUrl})`, d.logUrl \u0026amp;\u0026amp; `[Log Vercel](${d.logUrl})`, commitUrl \u0026amp;\u0026amp; `[Commit](${commitUrl})`, ].filter(Boolean).join(\u0026#39; · \u0026#39;); const fields = [ { name: \u0026#39;Environment\u0026#39;, value: d.isProd ? \u0026#39;🚀 Production\u0026#39; : \u0026#39;🧪 Preview\u0026#39;, inline: true }, { name: \u0026#39;Branch\u0026#39;, value: `\\`${d.ref}\\``, inline: true }, { name: \u0026#39;Commit\u0026#39;, value: shortSha ? `\\`${shortSha}\\`` : \u0026#39;-\u0026#39;, inline: true }, ]; if (!d.ok) fields.push({ name: \u0026#39;💡 Langkah\u0026#39;, value: \u0026#39;Cek build log di Vercel untuk melihat error-nya\u0026#39;, inline: false }); if (links) fields.push({ name: \u0026#39;🔗 Link\u0026#39;, value: links, inline: false }); const embed = { title: `${d.ok ? \u0026#39;✅ Deploy berhasil\u0026#39; : \u0026#39;❌ Deploy gagal\u0026#39;} · ${d.project}`, url: d.ok ? (d.siteUrl ?? d.logUrl) : d.logUrl, description: [message \u0026amp;\u0026amp; `**${trunc(message, 200)}**`, `Repo ${d.repo}`].filter(Boolean).join(\u0026#39;\\n\u0026#39;), color: d.ok ? 0x2ECC71 : 0xE03E2F, fields, footer: { text: \u0026#39;Vercel via GitHub\u0026#39; }, timestamp: new Date(d.createdAt ?? Date.now()).toISOString(), }; if (login || name) { embed.author = { name: login ? `${name ?? login} (@${login})` : name, url: login ? `https://github.com/${login}` : undefined, icon_url: avatar, }; } return [{ json: { username: \u0026#39;Vercel Deploy\u0026#39;, embeds: [embed] } }]; Catatan:\nPreview deploy bisa ramai. Satu PR bisa menghasilkan beberapa deploy preview. Kalau channel-nya cuma untuk rilis, set ONLY_PRODUCTION = true. Author yang tampil adalah author commit. Untuk squash merge, itu pembuat PR. Kalau username GitHub nggak muncul dan cuma nama, berarti email git config orang itu nggak terhubung ke akun GitHub-nya. Untuk tes ulang tanpa deploy baru, buka Recent Deliveries di halaman webhook GitHub, pilih event deployment_status yang sukses, lalu Redeliver. Cara ini cuma berlaku untuk deploy dari integrasi Git Vercel. Deploy lewat CLI dari pipeline CI nggak lewat GitHub, jadi notifikasinya lebih gampang ditambahkan di pipeline itu sendiri. Checklist produksi Setelah semua jalur jalan:\nAmankan webhook n8n. Saat ini siapa pun yang tahu URL bisa mengirim card palsu. Pasang Header Auth di node Webhook (Authorization: Bearer \u0026lt;token\u0026gt;, generate dengan openssl rand -hex 24) dan isi token yang sama di contact point Grafana atau Destination New Relic. Untuk GitHub, isi Secret dan verifikasi signature-nya. Siapkan Error Workflow di n8n. Karena semua alert lewat n8n, kalau n8n bermasalah alert nggak sampai. Buat workflow kecil dengan Error Trigger yang mengirim pesan ke Discord, lalu pasang sebagai Error Workflow. Kembalikan semua rule ke setting produksi setelah tes (time range 5 menit, interval 1m). Backup rule. Export alert rules Grafana ke YAML dan simpan di repo. Evaluasi setelah seminggu. Terlalu berisik? Naikkan threshold atau kecualikan endpoint tertentu. Deploy rutin sering memicu 502/503? Buat mute timing untuk jam deploy. Bonus: cek isi log kamu Waktu ngoprek ini saya sempat menemukan log level debug di production yang mencatat request/response lengkap ke pihak ketiga, termasuk data pribadi customer, dan satu baris yang mencatat nilai token rahasia secara plaintext. Siapa pun yang punya akses Kibana bisa membacanya.\nKalau kamu pakai OTel Collector, pasang redaksi sebagai jaring pengaman sebelum data dikirim ke backend:\nprocessors: transform/redact: log_statements: - context: log statements: - replace_pattern(body, \u0026#34;\\\u0026#34;email\\\u0026#34;:\\\u0026#34;[^\\\u0026#34;]*\\\u0026#34;\u0026#34;, \u0026#34;\\\u0026#34;email\\\u0026#34;:\\\u0026#34;***\\\u0026#34;\u0026#34;) - replace_pattern(body, \u0026#34;\\\u0026#34;phone_number\\\u0026#34;:\\\u0026#34;[^\\\u0026#34;]*\\\u0026#34;\u0026#34;, \u0026#34;\\\u0026#34;phone_number\\\u0026#34;:\\\u0026#34;***\\\u0026#34;\u0026#34;) - replace_pattern(body, \u0026#34;expected \\\\S+\u0026#34;, \u0026#34;expected ***\u0026#34;) Tapi perbaikan utamanya tetap di aplikasi: set log level production ke info, jangan pernah log nilai secret, dan rotasi secret yang sempat tercatat.\nSekarang semua alert masuk ke Discord dengan format yang sama, apa pun sumbernya. Menambah sumber baru juga gampang: tinggal satu Webhook trigger dan satu node Code lagi.\n","permalink":"https://rifkiaz.my.id/posts/alert-ke-discord-lewat-n8n/","summary":"\u003cp\u003eDi tim, alert kami tersebar di mana-mana: error aplikasi di Sentry, APM di Elastic, sebagian service di New Relic, dan deploy frontend di Vercel. Masing-masing punya integrasi Discord sendiri, tapi ada yang berbayar, ada yang terkunci lisensi, dan tampilannya beda-beda semua.\u003c/p\u003e\n\u003cp\u003eAkhirnya saya pakai satu pola untuk semuanya:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSumber alert → Webhook n8n → Code (format embed) → HTTP Request → Discord\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"Alur umum: Sentry, Grafana, New Relic, dan GitHub mengirim webhook ke n8n, lalu n8n mengirim embed card ke Discord\" loading=\"lazy\" src=\"/posts/alert-ke-discord-lewat-n8n/assets/alur-umum.svg\"\u003e\u003c/p\u003e","title":"Satu Pintu Alert ke Discord Pakai n8n: Sentry, Grafana, New Relic, dan Vercel"},{"content":"Di salah satu service, konfigurasi aplikasi diambil dari Vault lalu ditulis jadi file .env memakai template (consul-template / Vault Agent). Template awalnya kira-kira seperti ini:\n{{ with secret \u0026#34;secret/data/dev/app/my-service\u0026#34; }} {{ range $k, $v := .Data.data }} {{- if (contains \u0026#34;\\n\u0026#34; $v) -}} {{ $k }}=\u0026#34;{{ $v | replaceAll \u0026#34;\\n\u0026#34; \u0026#34;\\\\n\u0026#34; }}\u0026#34; {{- else -}} {{ $k }}=\u0026#34;{{ $v }}\u0026#34; {{- end }} {{ end }} {{ end }} Hasilnya, setiap value dibungkus tanda kutip:\nDB_HOST=\u0026#34;db.example.internal\u0026#34; API_KEY=\u0026#34;\u0026lt;api-key\u0026gt;\u0026#34; Lalu datang komplain dari tim aplikasi: konfigurasinya \u0026ldquo;nggak kebaca\u0026rdquo;. Koneksi database gagal, padahal value di Vault benar.\nKenapa kutipnya jadi masalah Jawabannya tergantung siapa yang membaca file itu, karena aturan parsing .env tidak seragam:\nCara baca Tanda kutip docker run --env-file ikut jadi bagian value source .env di bash dilepas godotenv dan library dotenv sejenis dilepas env_file di Docker Compose dilepas di versi Compose modern, tapi perilakunya pernah berbeda antar versi Jadi kalau file ini dipakai sebagai --env-file di Docker, DB_HOST berisi literal \u0026quot;db.example.internal\u0026quot; lengkap dengan kutipnya, dan tentu saja host itu nggak bisa di-resolve.\nTemplate yang diperbaiki Kutipnya dihilangkan, kecuali untuk value multiline seperti private key atau sertifikat. Value seperti itu tetap butuh kutip supaya \\n hasil escape tidak merusak format file:\n{{ with secret \u0026#34;secret/data/dev/app/my-service\u0026#34; }}{{ range $k, $v := .Data.data }}{{ if (contains \u0026#34;\\n\u0026#34; $v) }}{{ $k }}=\u0026#34;{{ $v | replaceAll \u0026#34;\\n\u0026#34; \u0026#34;\\\\n\u0026#34; }}\u0026#34; {{ else }}{{ $k }}={{ $v }} {{ end }}{{ end }}{{ end }} Template-nya sengaja ditulis rapat. Setiap baris baru di luar {{ }} ikut tercetak ke output, dan itu sumber baris-baris kosong di file hasil versi lama. Hasilnya sekarang:\nDB_HOST=db.example.internal API_KEY=\u0026lt;api-key\u0026gt; PRIVATE_KEY=\u0026#34;-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\u0026#34; Kalau di path itu sama sekali nggak ada value multiline, template-nya bisa lebih sederhana:\n{{ with secret \u0026#34;secret/data/dev/app/my-service\u0026#34; }}{{ range $k, $v := .Data.data }}{{ $k }}={{ $v }} {{ end }}{{ end }} Risiko tanpa kutip Menghapus kutip juga ada harganya, jadi cek dulu isi secret-nya sebelum apply:\nSpasi dan #. Di beberapa parser, value tanpa kutip yang mengandung spasi atau # bisa terpotong (bagian setelah # dianggap komentar). Backtick dan $. Di Docker env-file, karakter ini aman karena dibaca apa adanya. Tapi kalau ada yang menjalankan source .env di bash, backtick akan dieksekusi sebagai command substitution dan $VAR akan di-expand. Password acak sering mengandung karakter seperti ini. Kalau setelah perubahan ini ada service lain yang malah rusak, kemungkinan besar service itu membaca file dengan cara yang berbeda (misalnya di-source dari shell). Solusi paling bersih adalah memastikan satu file hanya dikonsumsi dengan satu cara, atau membuat template terpisah per konsumen.\nCatatan soal berbagi screenshot Waktu debugging seperti ini, gampang sekali seseorang mengirim screenshot isi .env ke chat untuk menunjukkan masalahnya. Padahal isinya access key cloud dan API key partner dalam bentuk terbaca. Kalau itu terjadi di channel yang diakses banyak orang, anggap key-nya sudah bocor: rotate key-nya, lalu hapus screenshot-nya. Untuk menunjukkan masalah format, cukup kirim nama key dengan value yang disamarkan.\n","permalink":"https://rifkiaz.my.id/posts/template-vault-env-file-tanpa-kutip/","summary":"\u003cp\u003eDi salah satu service, konfigurasi aplikasi diambil dari Vault lalu ditulis jadi file \u003ccode\u003e.env\u003c/code\u003e memakai template (consul-template / Vault Agent). Template awalnya kira-kira seperti ini:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ with secret \u0026#34;secret/data/dev/app/my-service\u0026#34; }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ range $k, $v := .Data.data }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{- if (contains \u0026#34;\\n\u0026#34; $v) -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ $k }}=\u0026#34;{{ $v | replaceAll \u0026#34;\\n\u0026#34; \u0026#34;\\\\n\u0026#34; }}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{- else -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ $k }}=\u0026#34;{{ $v }}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHasilnya, setiap value dibungkus tanda kutip:\u003c/p\u003e","title":"Template Vault untuk File .env: Kapan Pakai Tanda Kutip, Kapan Tidak"},{"content":"Disk root salah satu server MongoDB production saya sudah terpakai 85% (68 dari 80 GB). Sebelum mentok, saya perbesar jadi 120 GB. Kabar baiknya, di EC2 ini bisa dilakukan online: tanpa reboot, tanpa stop MongoDB.\nSatu hal yang sering bikin bingung: setelah ukuran volume diubah di console, df tetap menunjukkan ukuran lama. Itu karena ada tiga lapisan yang harus diperbesar, dan AWS cuma mengurus lapisan pertama.\nContoh di tulisan ini memakai Amazon Linux 2023 dengan root filesystem XFS di /dev/nvme0n1p1.\n0. Cek kondisi awal df -hT Filesystem Type Size Used Avail Use% Mounted on /dev/nvme0n1p1 xfs 80G 68G 13G 85% / tmpfs tmpfs 3.8G 0 3.8G 0% /tmp /dev/nvme0n1p128 vfat 10M 1.4M 8.7M 14% /boot/efi Dari sini kelihatan dua hal penting: tipe filesystem-nya XFS (menentukan perintah resize nanti), dan /tmp berupa tmpfs yang kosong. Yang kedua ini relevan karena growpart butuh sedikit ruang di /tmp; kalau disk root sudah mentok 100% dan /tmp ikut di root, growpart bisa gagal.\nSebelum resize, ada baiknya juga cek apa yang makan tempat. Kalau ternyata log atau journal yang menumpuk, menambah disk cuma menunda masalah:\nsudo du -xh --max-depth=1 / | sort -rh | head -20 sudo du -xh --max-depth=1 /var/log | sort -rh | head 1. Snapshot dulu Ini production, jadi jangan dilewati. Di EC2 → Instances, pilih instance-nya, buka tab Storage, klik Volume ID root device, lalu Actions → Create snapshot.\nLewat CLI:\naws ec2 create-snapshot --volume-id \u0026lt;volume-id\u0026gt; \\ --description \u0026#34;pre-resize $(date +%F)\u0026#34; Snapshot pertama sebuah volume itu full copy ke S3, jadi bisa makan 15–40 menit dan statusnya lama di pending. Kamu nggak perlu menunggu sampai completed. Snapshot EBS bersifat point-in-time: begitu dibuat, kondisi datanya sudah terkunci pada momen itu. Status pending cuma berarti transfer ke S3 masih jalan di background, dan resize nggak mengganggu snapshot yang sedang berjalan.\nSatu catatan: snapshot ini konsisten di level crash, bukan aplikasi. Kalau di-restore, hasilnya setara server yang mati mendadak; MongoDB akan replay journal saat start dan biasanya baik-baik saja. Kalau butuh snapshot yang benar-benar bersih untuk MongoDB standalone, jalankan db.fsyncLock() sebelum snapshot dan db.fsyncUnlock() setelahnya. Untuk jaring pengaman sebelum resize, snapshot biasa sudah cukup.\n2. Modify volume Di halaman volume: Actions → Modify volume, ubah Size dari 80 ke 120, lalu Modify → Confirm.\nLewat CLI:\naws ec2 modify-volume --volume-id \u0026lt;volume-id\u0026gt; --size 120 # pantau progresnya aws ec2 describe-volumes-modifications --volume-id \u0026lt;volume-id\u0026gt; \\ --query \u0026#39;VolumesModifications[].{state:ModificationState,progress:Progress}\u0026#39; Tunggu sampai state jadi optimizing (di console: in-use - optimizing). Begitu masuk optimizing, kapasitas baru sudah bisa dipakai, nggak perlu tunggu completed.\nSekalian pindah ke gp3? Kalau volume-nya masih gp2, ganti Volume type ke gp3 di form yang sama. Alasannya:\nPerforma. gp2 memberi IOPS proporsional ukuran: 3 IOPS per GB. Di 80 GB itu cuma baseline 240 IOPS (120 GB pun baru 360), sisanya bergantung burst credit yang bisa habis. gp3 memberi 3000 IOPS dan 125 MB/s tetap, berapa pun ukurannya. Untuk database yang datanya jauh lebih besar dari RAM (sering baca dari disk), ini langsung terasa di latensi query. Harga. gp3 sekitar 20% lebih murah per GB. Pindahnya online, tanpa reboot, dan nggak menyentuh data. Yang penting, lakukan dalam satu operasi Modify yang sama, karena setelah modify selesai volume kena cooldown sekitar 6 jam sebelum bisa diubah lagi.\ngp2 cuma lebih menarik untuk disk yang sangat besar (di atas ~1 TB, IOPS-nya naik otomatis), dan itu jarang berlaku untuk root disk.\n3. Perbesar partisi Masuk ke instance (SSH atau Connect → Session Manager). Bagian ini nggak ada gantinya di console.\nlsblk Yang dicari: disk nvme0n1 sudah 120G, tapi partisi nvme0n1p1 masih 80G. Kalau disk-nya masih 80G juga, berarti modify volume belum sampai optimizing; tunggu sebentar.\nsudo growpart /dev/nvme0n1 1 Perhatikan spasi antara nama disk dan angka 1. Outputnya kira-kira CHANGED: partition=1 start=... old: size=... new: size=....\nSoal partisi EFI nvme0n1p128: di Amazon Linux 2023 letaknya di awal disk, jadi p1 tetap partisi terakhir dan growpart bisa langsung memakai ruang kosong di belakangnya. Kalau mau memastikan dulu:\nsudo sgdisk -p /dev/nvme0n1 p1 seharusnya punya End sector paling besar.\nKalau growpart bilang NOCHANGE: partition 1 ... cannot be grown, OS belum melihat ukuran barunya. Refresh dengan:\nsudo sh -c \u0026#39;echo 1 \u0026gt; /sys/class/block/nvme0n1/device/rescan_controller\u0026#39; lalu ulangi lsblk.\n4. Perbesar filesystem Untuk XFS (default Amazon Linux):\nsudo xfs_growfs -d / XFS di-grow dalam keadaan mounted, dan inilah yang membuat prosesnya tanpa downtime. Kalau filesystem kamu ext4, perintahnya:\nsudo resize2fs /dev/nvme0n1p1 5. Verifikasi df -hT / / sekarang sekitar 120G, dan Use% turun dari 85% ke sekitar 57%. MongoDB jalan terus selama proses ini.\nRingkasan Langkah Di mana Perintah / menu Snapshot AWS Actions → Create snapshot (nggak perlu tunggu completed) Resize volume (+ gp3) AWS Actions → Modify volume, tunggu optimizing Perbesar partisi Instance sudo growpart /dev/nvme0n1 1 Perbesar filesystem Instance sudo xfs_growfs -d / (XFS) atau resize2fs (ext4) Verifikasi Instance df -hT / Terakhir, menambah disk itu solusi jangka pendek. Kalau pertumbuhannya dari data (bukan log), apalagi datanya sudah jauh melebihi RAM instance, pantau trennya dan pertimbangkan juga upsize instance atau strategi retensi data, supaya kamu nggak mengulang langkah ini tiap beberapa bulan.\n","permalink":"https://rifkiaz.my.id/posts/resize-disk-ebs-ec2-tanpa-downtime/","summary":"\u003cp\u003eDisk root salah satu server MongoDB production saya sudah terpakai 85% (68 dari 80 GB). Sebelum mentok, saya perbesar jadi 120 GB. Kabar baiknya, di EC2 ini bisa dilakukan \u003cstrong\u003eonline\u003c/strong\u003e: tanpa reboot, tanpa stop MongoDB.\u003c/p\u003e\n\u003cp\u003eSatu hal yang sering bikin bingung: setelah ukuran volume diubah di console, \u003ccode\u003edf\u003c/code\u003e tetap menunjukkan ukuran lama. Itu karena ada tiga lapisan yang harus diperbesar, dan AWS cuma mengurus lapisan pertama.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Alur resize disk EBS: snapshot dan modify volume di AWS, lalu growpart dan xfs_growfs di dalam instance\" loading=\"lazy\" src=\"/posts/resize-disk-ebs-ec2-tanpa-downtime/assets/alur-resize.svg\"\u003e\u003c/p\u003e","title":"Memperbesar Disk EC2 (EBS) Tanpa Downtime"},{"content":"Beberapa waktu lalu saya pasang openSUSE Tumbleweed dengan GNOME di ThinkPad X1 Nano Gen 1. Secara umum mulus, tapi ada tiga hal yang bikin saya cukup lama ngoprek:\nLayar login (GDM) tetap di-scale 150%, padahal sesi GNOME sudah 100%. Mikrofon hilang setelah pasang sof-firmware. X1 Nano punya 4 speaker, tapi yang \u0026ldquo;terdeteksi\u0026rdquo; cuma 2 dan suaranya pelan dibanding Windows. Tulisan ini merangkum apa yang ternyata jadi penyebab, termasuk jalan buntu yang sempat saya lewati. Siapa tahu kamu kena masalah yang sama.\n1. GDM tetap di 150% Panel X1 Nano itu 2160×1350 di layar 13 inci, jadi GNOME otomatis memilih scale 150%. Di sesi desktop saya ubah ke 100% lewat Settings → Displays, aman. Tapi layar login tetap 150%.\nPenyebab dasarnya: GDM jalan sebagai user gdm sendiri dengan konfigurasi terpisah, jadi setting display user kamu nggak ikut terbawa. Solusi klasiknya adalah menyalin ~/.config/monitors.xml ke home milik gdm.\nPastikan monitors.xml kamu benar dulu File ini baru dibuat kalau kamu pernah menekan Apply di Settings → Displays. Cek isinya:\ncat ~/.config/monitors.xml Yang perlu diperhatikan adalah nilai \u0026lt;scale\u0026gt;. Kalau masih 1.5, ya menyalinnya ke GDM nggak akan mengubah apa-apa. Punya saya kira-kira begini (bagian vendor/product disederhanakan):\n\u0026lt;monitors version=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;layoutmode\u0026gt;logical\u0026lt;/layoutmode\u0026gt; \u0026lt;logicalmonitor\u0026gt; \u0026lt;x\u0026gt;0\u0026lt;/x\u0026gt; \u0026lt;y\u0026gt;0\u0026lt;/y\u0026gt; \u0026lt;scale\u0026gt;1\u0026lt;/scale\u0026gt; \u0026lt;primary\u0026gt;yes\u0026lt;/primary\u0026gt; \u0026lt;monitor\u0026gt; \u0026lt;monitorspec\u0026gt; \u0026lt;connector\u0026gt;eDP-1\u0026lt;/connector\u0026gt; \u0026lt;vendor\u0026gt;...\u0026lt;/vendor\u0026gt; \u0026lt;product\u0026gt;...\u0026lt;/product\u0026gt; \u0026lt;serial\u0026gt;...\u0026lt;/serial\u0026gt; \u0026lt;/monitorspec\u0026gt; \u0026lt;mode\u0026gt; \u0026lt;width\u0026gt;2160\u0026lt;/width\u0026gt; \u0026lt;height\u0026gt;1350\u0026lt;/height\u0026gt; \u0026lt;rate\u0026gt;59.744\u0026lt;/rate\u0026gt; \u0026lt;/mode\u0026gt; \u0026lt;/monitor\u0026gt; \u0026lt;/logicalmonitor\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/monitors\u0026gt; Sebaiknya jangan tulis file ini manual. connector, vendor, product, serial, dan rate harus persis sama dengan yang dilaporkan mutter. Kalau satu saja meleset (misalnya rate dibulatkan jadi 60), mutter diam-diam membuang seluruh konfigurasi dan balik ke auto-scale.\nJebakan 1: paste multi-baris di terminal Ini agak memalukan tapi patut diceritakan. Saya sempat yakin sudah menyalin file-nya, tapi ternyata ls bilang file-nya nggak ada. Waktu dilihat lagi, baris-baris perintah yang saya paste sekaligus ternyata nempel jadi satu (... restart display-manageruser@host:~\u0026gt;), jadi cp-nya nggak pernah benar-benar jalan.\nKalau terminal kamu bermasalah dengan paste multi-baris, sambung saja dengan \u0026amp;\u0026amp; di satu baris, plus penanda di akhir supaya kelihatan berhasil atau tidak:\nsudo cp ~/.config/monitors.xml /var/lib/gdm/.config/ \u0026amp;\u0026amp; sudo chown -R gdm:gdm /var/lib/gdm/.config \u0026amp;\u0026amp; sudo restorecon -Rv /var/lib/gdm/.config \u0026amp;\u0026amp; echo BERHASIL Jebakan 2: SELinux Tumbleweed sekarang default-nya SELinux enforcing (kelihatan dari titik di akhir permission, misalnya -rw-r--r--.). File yang di-cp dari home bisa membawa context user_home_t yang belum tentu boleh dibaca GDM. Makanya ada restorecon di perintah di atas. Cek hasilnya:\nsudo ls -laZ /var/lib/gdm/.config/ Context-nya harus xdm_var_lib_t dan owner-nya gdm gdm.\nJebakan 3: path-nya sudah pindah di GDM baru Setelah file benar-benar ada, owner dan SELinux sudah tepat, dan display manager sudah di-restart, layar login tetap 150%. journalctl juga sama sekali nggak menyebut monitor config, artinya file itu memang nggak pernah dibaca.\nTernyata menurut laporan di issue tracker GDM, sejak GDM 49 file /var/lib/gdm/.config/monitors.xml sudah nggak dibaca lagi. Sistem saya pakai mutter 50, jadi pasti kena. Cek versimu dengan:\nmutter --version Ada dua lokasi yang bisa dicoba. Yang paling bersih adalah fallback sistem-wide di /etc/xdg, karena nggak bergantung pada home gdm sama sekali:\nsudo cp ~/.config/monitors.xml /etc/xdg/monitors.xml \u0026amp;\u0026amp; sudo chmod 644 /etc/xdg/monitors.xml \u0026amp;\u0026amp; echo OK Kalau belum mempan, coba lokasi baru per-seat:\nsudo mkdir -p /var/lib/gdm/seat0/config \u0026amp;\u0026amp; sudo cp ~/.config/monitors.xml /var/lib/gdm/seat0/config/ \u0026amp;\u0026amp; sudo chown -R gdm:gdm /var/lib/gdm/seat0/config \u0026amp;\u0026amp; sudo restorecon -Rv /var/lib/gdm/seat0/config \u0026amp;\u0026amp; echo OK Lalu restart display manager. Hati-hati, perintah ini langsung menutup sesi kamu, jadi simpan pekerjaan dulu:\nsudo systemctl restart display-manager Ada juga laporan bahwa permission direktori seat0/config bisa ter-reset sendiri, jadi saya pribadi lebih memilih /etc/xdg.\nSoal dconf Banyak tutorial menyarankan override lewat /etc/dconf/db/gdm.d/ (misalnya mematikan experimental-features mutter atau mengatur scaling-factor). Di openSUSE, file /etc/dconf/profile/gdm defaultnya nggak ada, jadi database dconf itu nggak dipakai sama sekali. Selain itu, ArchWiki memperingatkan bahwa mengubah experimental-features mutter bisa bikin monitors.xml jadi nggak kompatibel. Kalau kamu sempat membuat file seperti itu, mending hapus saja:\nsudo rm /etc/dconf/db/gdm.d/10-scale \u0026amp;\u0026amp; sudo dconf update Satu catatan lagi: dengan scale 1, layar login berjalan di 2160×1350 penuh, jadi teksnya terasa kecil di panel 13 inci. Itu konsekuensi wajar dari pilihan 100%.\n2. Mikrofon hilang setelah pasang sof-firmware Setelah memasang sof-firmware, mikrofon malah \u0026ldquo;mute\u0026rdquo;. Sebelum ngoprek jauh, cek dulu hal yang paling sepele: ThinkPad punya tombol mic mute (F4) dengan LED oranye, dan statusnya bertahan lintas reboot.\nKalau bukan itu, cek berlapis dari kernel ke atas.\nKernel / SOF. Pastikan firmware dan topology ter-load tanpa error:\nsudo dmesg | grep -i -E \u0026#34;sof|snd_soc|firmware\u0026#34; | head -30 Di punya saya sisi kernel sebenarnya sudah sempurna:\nDMICs detected in NHLT tables: 4 Topology file: intel/sof-tplg/sof-hda-generic-4ch.tplg PipeWire. Lihat apa yang dikenali sebagai sumber input:\nwpctl status Di bagian Sources cuma ada \u0026ldquo;HD Audio Stereo\u0026rdquo;, nggak ada \u0026ldquo;Digital Microphone\u0026rdquo;. Perintah wpctl get-volume @DEFAULT_AUDIO_SOURCE@ malah error '-1' is not a valid ID. Jadi masalahnya bukan mute, tapi mic-nya memang nggak di-expose.\nUCM. Ini dia biang keroknya:\nzypper info alsa-ucm-conf sof-firmware Hasilnya alsa-ucm-conf ternyata tidak terpasang. UCM (Use Case Manager) adalah lapisan yang memberi tahu PipeWire/WirePlumber bagaimana meng-expose DMIC array sebagai perangkat input. Tanpa UCM, WirePlumber cuma melihat jalur HDA generik.\nPerbaikannya:\nsudo zypper install alsa-ucm-conf alsa-utils systemctl --user restart wireplumber pipewire pipewire-pulse alsa-utils sekalian dipasang karena alsamixer dan alsactl juga belum ada. Setelah restart, wpctl status langsung menampilkan Digital Microphone sebagai source default. Bonusnya, sink-nya juga jadi benar: dari satu \u0026ldquo;Stereo\u0026rdquo; generik jadi Speaker plus beberapa output HDMI/DisplayPort. Jadi UCM yang hilang itu merusak input dan output sekaligus.\nTes cepat mic-nya:\narecord -f cd -d 5 /tmp/tes.wav \u0026amp;\u0026amp; aplay /tmp/tes.wav Kalau masih senyap, buka alsamixer -c 0, tekan F4 untuk view Capture, dan pastikan Dmic0 tidak berstatus MM (muted).\nKarena alsa-ucm-conf biasanya ikut sebagai dependency, hilangnya paket ini agak aneh. Setelah beres, ada baiknya jalankan sudo zypper dup untuk memastikan nggak ada paket lain yang tertinggal.\n3. Empat speaker, tapi kok cuma dua? X1 Nano punya 2 tweeter di atas dan 2 woofer di bawah, di-drive codec Realtek ALC287 lewat dua DAC. Di PipeWire yang muncul cuma sink stereo, jadi saya kira dua speaker nggak jalan.\nCek dulu kontrol mixer-nya:\namixer -c 0 scontrols for c in Master Speaker \u0026#34;Bass Speaker\u0026#34; DAC1 DAC2 \u0026#34;Auto-Mute Mode\u0026#34;; do amixer -c 0 sget \u0026#34;$c\u0026#34;; done Di punya saya Speaker dan Bass Speaker sudah [on], DAC1 dan DAC2 sama-sama 100%, dan Auto-Mute Mode sudah Disabled. Nggak ada yang perlu diperbaiki.\nTernyata keempat speaker memang sudah bunyi. Empat speaker itu dirangkai sebagai dua pasang stereo yang menerima stream yang sama, jadi wajar kalau PipeWire cuma menampilkan satu sink stereo. Cara membuktikannya: putar suara, lalu matikan salah satu pasangan di terminal lain.\nspeaker-test -D hw:0,0 -c 2 -t wav -l 10 amixer -c 0 sset \u0026#39;Bass Speaker\u0026#39; mute # suara jadi tipis? berarti woofer aktif amixer -c 0 sset \u0026#39;Bass Speaker\u0026#39; unmute Kalau tiap pasangan yang di-mute mengubah suara, berarti keempat speaker kamu berfungsi. Kamu nggak perlu memaksa quirk kernel seperti model=alc285-speaker2-to-dac1. Quirk itu justru bisa menurunkan volume maksimum dan bikin kontrol volume kacau.\nKenapa lebih pelan dibanding Windows? Di Windows, Lenovo memasang lapisan DSP (Dolby / efek audio Realtek) yang melakukan compression, bass boost, dan limiting. Speaker yang sama jadi terdengar jauh lebih keras dan penuh. Di Linux, yang kamu dengar adalah output mentah tanpa pemrosesan. Beberapa hal yang bisa dilakukan:\nNaikkan Master. Punya saya ternyata masih di 82% (-12 dB):\namixer -c 0 sset Master 100% \u0026amp;\u0026amp; sudo alsactl store Over-amplification. Aktifkan di Settings → Sound supaya volume bisa sampai 150%. Ingat, di atas 100% itu gain digital murni, jadi bisa pecah kalau berlebihan.\nEasyEffects. Ini yang paling mendekati pengalaman Windows:\nsudo zypper install easyeffects Di tab Output, tambahkan chain Bass Enhancer → Compressor → Limiter, lalu aktifkan \u0026ldquo;Start Service at Login\u0026rdquo;. Compressor menaikkan bagian yang pelan tanpa bikin yang keras jadi pecah, dan Limiter mencegah clipping. Kamu juga bisa cari preset komunitas untuk X1 Nano atau X1 Carbon Gen 9 (hardware audionya mirip) lalu taruh di ~/.config/easyeffects/output/.\nEkspektasinya tetap harus realistis. Speaker X1 Nano memang kecil, dan sebagian \u0026ldquo;kerasnya\u0026rdquo; di Windows itu efek psikoakustik dari bass boost, bukan tenaga sesungguhnya.\nRangkuman Masalah Penyebab Solusi GDM tetap 150% Sejak GDM 49 /var/lib/gdm/.config/monitors.xml tidak dibaca Salin monitors.xml ke /etc/xdg/ (atau /var/lib/gdm/seat0/config/) Mikrofon hilang alsa-ucm-conf tidak terpasang zypper install alsa-ucm-conf alsa-utils, restart WirePlumber Speaker \u0026ldquo;cuma 2\u0026rdquo; Normal, 4 speaker = 2 pasang stereo Tidak perlu quirk; pakai EasyEffects untuk loudness Pelajaran terbesarnya buat saya: jangan langsung percaya perintah sudah jalan. Cek hasilnya (ls, echo $?, journalctl) sebelum lanjut ke tebakan berikutnya. Setengah waktu saya habis karena cp yang ternyata nggak pernah dieksekusi.\n","permalink":"https://rifkiaz.my.id/posts/opensuse-tumbleweed-thinkpad-x1-nano/","summary":"\u003cp\u003eBeberapa waktu lalu saya pasang openSUSE Tumbleweed dengan GNOME di ThinkPad X1 Nano Gen 1. Secara umum mulus, tapi ada tiga hal yang bikin saya cukup lama ngoprek:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eLayar login (GDM) tetap di-scale 150%, padahal sesi GNOME sudah 100%.\u003c/li\u003e\n\u003cli\u003eMikrofon hilang setelah pasang \u003ccode\u003esof-firmware\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eX1 Nano punya 4 speaker, tapi yang \u0026ldquo;terdeteksi\u0026rdquo; cuma 2 dan suaranya pelan dibanding Windows.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eTulisan ini merangkum apa yang ternyata jadi penyebab, termasuk jalan buntu yang sempat saya lewati. Siapa tahu kamu kena masalah yang sama.\u003c/p\u003e","title":"openSUSE Tumbleweed di ThinkPad X1 Nano: Benerin Scaling GDM, Mikrofon, dan Speaker"},{"content":"Monitoring yang enak dipakai itu dimulai dari metrik yang konsisten di semua server. Kalau node_exporter dipasang manual satu per satu di puluhan host, ujung-ujungnya versinya beda-beda, flag-nya beda, atau ada yang lupa di-enable sehingga mati setelah reboot. Di tulisan ini saya mau berbagi role Ansible sederhana untuk memasang exporter Prometheus supaya hasilnya selalu sama.\nContohnya pakai node_exporter, tapi polanya bisa dipakai juga untuk exporter lain yang bentuknya satu binary, misalnya exporter MongoDB, MySQL, atau HAProxy.\nPrinsip yang saya pegang Versi dikunci di variabel, bukan \u0026ldquo;latest\u0026rdquo;. Checksum dicek dulu sebelum binary dipasang. Jalan pakai user khusus tanpa shell, bukan root. Dikelola systemd, jadi otomatis hidup lagi setelah reboot. Nggak terbuka ke publik. Port exporter cuma bisa diakses server Prometheus. Struktur role roles/node_exporter/ ├── defaults/main.yml ├── handlers/main.yml ├── tasks/main.yml └── templates/node_exporter.service.j2 Variabel # roles/node_exporter/defaults/main.yml node_exporter_version: \u0026#34;1.9.1\u0026#34; node_exporter_arch: amd64 node_exporter_user: node_exporter node_exporter_bin: /usr/local/bin/node_exporter node_exporter_listen_address: \u0026#34;{{ ansible_default_ipv4.address }}:9100\u0026#34; node_exporter_extra_flags: - --collector.systemd Kalau mau upgrade, cek versi terbaru di halaman rilis resmi lalu ganti node_exporter_version secara manual. Dengan begitu upgrade selalu disengaja, bukan kejutan.\nTask # roles/node_exporter/tasks/main.yml - name: Buat user sistem untuk node_exporter ansible.builtin.user: name: \u0026#34;{{ node_exporter_user }}\u0026#34; system: true shell: /usr/sbin/nologin create_home: false - name: Unduh arsip node_exporter dengan verifikasi checksum ansible.builtin.get_url: url: \u0026#34;https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}.tar.gz\u0026#34; dest: \u0026#34;/tmp/node_exporter-{{ node_exporter_version }}.tar.gz\u0026#34; checksum: \u0026#34;sha256:https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/sha256sums.txt\u0026#34; mode: \u0026#34;0644\u0026#34; - name: Ekstrak arsip ansible.builtin.unarchive: src: \u0026#34;/tmp/node_exporter-{{ node_exporter_version }}.tar.gz\u0026#34; dest: /tmp remote_src: true creates: \u0026#34;/tmp/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}/node_exporter\u0026#34; - name: Pasang binary node_exporter ansible.builtin.copy: src: \u0026#34;/tmp/node_exporter-{{ node_exporter_version }}.linux-{{ node_exporter_arch }}/node_exporter\u0026#34; dest: \u0026#34;{{ node_exporter_bin }}\u0026#34; remote_src: true owner: root group: root mode: \u0026#34;0755\u0026#34; notify: Restart node_exporter - name: Pasang unit systemd ansible.builtin.template: src: node_exporter.service.j2 dest: /etc/systemd/system/node_exporter.service owner: root group: root mode: \u0026#34;0644\u0026#34; notify: Restart node_exporter - name: Aktifkan dan jalankan node_exporter ansible.builtin.systemd_service: name: node_exporter state: started enabled: true daemon_reload: true - name: Terapkan restart bila ada perubahan ansible.builtin.meta: flush_handlers - name: Pastikan endpoint metrics merespons ansible.builtin.uri: url: \u0026#34;http://{{ node_exporter_listen_address }}/metrics\u0026#34; status_code: 200 register: node_exporter_metrics until: node_exporter_metrics.status == 200 retries: 5 delay: 3 Unit systemd # roles/node_exporter/templates/node_exporter.service.j2 [Unit] Description=Prometheus Node Exporter Wants=network-online.target After=network-online.target [Service] User={{ node_exporter_user }} Group={{ node_exporter_user }} Type=simple ExecStart={{ node_exporter_bin }} \\ --web.listen-address={{ node_exporter_listen_address }}{% for flag in node_exporter_extra_flags %} \\ {{ flag }}{% endfor %} Restart=on-failure NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true [Install] WantedBy=multi-user.target Opsi hardening seperti ProtectSystem dan NoNewPrivileges gampang ditambahkan, dan cukup membantu membatasi dampaknya kalau suatu saat ada celah di exporter.\nHandler # roles/node_exporter/handlers/main.yml - name: Restart node_exporter ansible.builtin.systemd_service: name: node_exporter state: restarted daemon_reload: true Playbook # monitoring.yml - name: Pasang node_exporter hosts: all become: true roles: - node_exporter ansible-playbook -i inventory/hosts.yml monitoring.yml Target scrape dari inventory Supaya daftar target Prometheus nggak perlu ditulis manual, saya generate dari inventory yang sama. Contoh template untuk file_sd:\n# roles/prometheus_targets/templates/node.yml.j2 - targets: {% for host in groups[\u0026#39;all\u0026#39;] %} - \u0026#34;{{ hostvars[host][\u0026#39;ansible_default_ipv4\u0026#39;][\u0026#39;address\u0026#39;] }}:9100\u0026#34; {% endfor %} labels: job: node Lalu di prometheus.yml:\nscrape_configs: - job_name: node file_sd_configs: - files: - /etc/prometheus/targets/node.yml Prometheus otomatis membaca ulang file file_sd, jadi kalau ada host baru, cukup tambahkan ke inventory lalu jalankan playbook. Satu catatan: template ini butuh fakta dari semua host, jadi pastikan fact gathering sudah jalan untuk semua host di play yang sama.\nKeamanan Buka port 9100 di firewall hanya untuk alamat server Prometheus. Kalau jaringannya nggak sepenuhnya bisa dipercaya, aktifkan TLS dan basic auth lewat --web.config.file, yang sudah didukung exporter resmi Prometheus. Matikan collector yang nggak dipakai. Makin sedikit metrik, makin ringan bebannya dan makin sedikit informasi yang terekspos. Alternatif: collection resmi Kalau nggak mau repot memelihara role sendiri, collection prometheus.prometheus di Ansible Galaxy sudah punya role untuk node_exporter dan beberapa exporter lain. Tapi bikin role sendiri tetap ada gunanya: kita jadi paham apa yang terjadi di balik layar, dan bisa dipakai untuk exporter yang belum ada di collection itu.\nExporter ini cocok dipasangkan dengan MongoDB replica set yang dipasang pakai Ansible. Metrik host dari node_exporter, metrik database dari exporter MongoDB, dan semuanya dipasang dari satu repository otomasi.\n","permalink":"https://rifkiaz.my.id/posts/ansible-prometheus-exporter/","summary":"\u003cp\u003eMonitoring yang enak dipakai itu dimulai dari metrik yang konsisten di semua server. Kalau \u003ccode\u003enode_exporter\u003c/code\u003e dipasang manual satu per satu di puluhan host, ujung-ujungnya versinya beda-beda, flag-nya beda, atau ada yang lupa di-enable sehingga mati setelah reboot. Di tulisan ini saya mau berbagi role Ansible sederhana untuk memasang exporter Prometheus supaya hasilnya selalu sama.\u003c/p\u003e\n\u003cp\u003eContohnya pakai \u003ccode\u003enode_exporter\u003c/code\u003e, tapi polanya bisa dipakai juga untuk exporter lain yang bentuknya satu binary, misalnya exporter MongoDB, MySQL, atau HAProxy.\u003c/p\u003e","title":"Memasang Prometheus Exporter dengan Ansible"},{"content":"Pasang satu server MongoDB secara manual itu gampang. Tapi kalau harus pasang tiga node replica set dengan konfigurasi yang sama persis, keyfile yang sama, autentikasi aktif, dan bisa dijalankan ulang kapan saja tanpa merusak cluster yang sudah jalan, ceritanya jadi lain. Di tulisan ini saya mau berbagi cara saya mengotomasinya pakai Ansible.\nOh iya, semua nama host, user, dan nilai di bawah cuma contoh, jadi sesuaikan saja dengan lingkunganmu.\nYang mau dicapai Tiga node MongoDB (satu primary, dua secondary) dalam replica set rs0. Autentikasi internal antar-node menggunakan keyfile. authorization aktif, dengan satu user admin. Playbook idempoten: mau dijalankan berapa kali pun hasilnya tetap sama, dan nggak mencoba rs.initiate() lagi di cluster yang sudah hidup. Rahasia seperti password dan keyfile disimpan di Ansible Vault, bukan ditaruh di repository sebagai teks biasa. Prasyarat Tiga server Ubuntu LTS yang bisa saling terhubung di port 27017 dan bisa dipanggil lewat nama host. Ansible di mesin kontrol, ditambah collection community.mongodb: ansible-galaxy collection install community.mongodb Modul-modul di collection ini butuh pymongo di node target. Sebelum pasang, cek dulu versi yang didukung di dokumentasi collection-nya.\nStruktur proyek mongodb-ansible/ ├── inventory/ │ ├── hosts.yml │ └── group_vars/ │ └── mongodb/ │ ├── main.yml │ └── vault.yml # terenkripsi dengan ansible-vault ├── roles/ │ └── mongodb/ │ ├── defaults/main.yml │ ├── handlers/main.yml │ ├── tasks/ │ │ ├── main.yml │ │ ├── install.yml │ │ ├── configure.yml │ │ ├── replicaset.yml │ │ └── users.yml │ └── templates/mongod.conf.j2 └── site.yml Inventory # inventory/hosts.yml all: children: mongodb: hosts: mongo-01: mongo-02: mongo-03: Node pertama di grup nantinya dipakai untuk inisialisasi replica set, dan saya kasih prioritas lebih tinggi supaya dia yang terpilih jadi primary. Daftar anggotanya saya tulis jelas di group_vars biar gampang dibaca:\n# inventory/group_vars/mongodb/main.yml mongodb_members: - host: \u0026#34;mongo-01:27017\u0026#34; priority: 2 - host: \u0026#34;mongo-02:27017\u0026#34; - host: \u0026#34;mongo-03:27017\u0026#34; Variabel dan rahasia # roles/mongodb/defaults/main.yml mongodb_version: \u0026#34;8.0\u0026#34; mongodb_user: mongodb mongodb_port: 27017 mongodb_bind_ip: \u0026#34;127.0.0.1,{{ ansible_default_ipv4.address }}\u0026#34; mongodb_dbpath: /var/lib/mongodb mongodb_replset_name: rs0 mongodb_keyfile_path: /etc/mongodb/keyfile mongodb_admin_user: admin Keyfile dan password admin masuk ke Vault. Isi keyfile cukup dibuat sekali:\nopenssl rand -base64 756 Lalu simpan di vault.yml dan enkripsi:\n# inventory/group_vars/mongodb/vault.yml (sebelum dienkripsi) mongodb_admin_password: \u0026#34;ganti-dengan-password-kuat\u0026#34; mongodb_keyfile_content: | \u0026lt;hasil openssl rand -base64 756\u0026gt; ansible-vault encrypt inventory/group_vars/mongodb/vault.yml Instalasi # roles/mongodb/tasks/install.yml - name: Pasang GPG key repository MongoDB ansible.builtin.get_url: url: \u0026#34;https://pgp.mongodb.com/server-{{ mongodb_version }}.asc\u0026#34; dest: \u0026#34;/usr/share/keyrings/mongodb-server-{{ mongodb_version }}.asc\u0026#34; mode: \u0026#34;0644\u0026#34; - name: Tambahkan repository MongoDB ansible.builtin.apt_repository: repo: \u0026gt;- deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-{{ mongodb_version }}.asc ] https://repo.mongodb.org/apt/ubuntu {{ ansible_distribution_release }}/mongodb-org/{{ mongodb_version }} multiverse filename: \u0026#34;mongodb-org-{{ mongodb_version }}\u0026#34; state: present - name: Install MongoDB dan pymongo ansible.builtin.apt: name: - mongodb-org - python3-pymongo state: present update_cache: true Konfigurasi dan keyfile # roles/mongodb/templates/mongod.conf.j2 storage: dbPath: {{ mongodb_dbpath }} systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log net: port: {{ mongodb_port }} bindIp: {{ mongodb_bind_ip }} processManagement: timeZoneInfo: /usr/share/zoneinfo security: authorization: enabled keyFile: {{ mongodb_keyfile_path }} replication: replSetName: {{ mongodb_replset_name }} # roles/mongodb/tasks/configure.yml - name: Buat direktori keyfile ansible.builtin.file: path: \u0026#34;{{ mongodb_keyfile_path | dirname }}\u0026#34; state: directory owner: \u0026#34;{{ mongodb_user }}\u0026#34; group: \u0026#34;{{ mongodb_user }}\u0026#34; mode: \u0026#34;0700\u0026#34; - name: Pasang keyfile replica set ansible.builtin.copy: content: \u0026#34;{{ mongodb_keyfile_content }}\u0026#34; dest: \u0026#34;{{ mongodb_keyfile_path }}\u0026#34; owner: \u0026#34;{{ mongodb_user }}\u0026#34; group: \u0026#34;{{ mongodb_user }}\u0026#34; mode: \u0026#34;0400\u0026#34; no_log: true notify: Restart mongod - name: Tulis mongod.conf ansible.builtin.template: src: mongod.conf.j2 dest: /etc/mongod.conf owner: root group: root mode: \u0026#34;0644\u0026#34; notify: Restart mongod # roles/mongodb/handlers/main.yml - name: Restart mongod ansible.builtin.systemd_service: name: mongod state: restarted Yang perlu diingat, keyfile harus sama persis di semua node dan cuma boleh dibaca user mongodb. Kalau permission-nya terlalu longgar, mongod bakal menolak jalan.\nInisialisasi replica set yang idempoten Nah, bagian ini yang paling sering bikin playbook gagal di run kedua. Begitu user admin dibuat, localhost exception langsung tertutup, jadi perintah yang tadinya bisa jalan tanpa login sekarang minta kredensial. Triknya, cek status pakai db.hello(). Perintah ini nggak butuh autentikasi dan akan mengembalikan setName kalau replica set sudah aktif.\n# roles/mongodb/tasks/replicaset.yml - name: Cek apakah replica set sudah diinisialisasi ansible.builtin.command: \u0026gt;- mongosh --quiet --port {{ mongodb_port }} --eval \u0026#34;db.hello().setName || \u0026#39;\u0026#39;\u0026#34; register: mongodb_rs_check changed_when: false run_once: true - name: Inisialisasi replica set community.mongodb.mongodb_replicaset: login_host: localhost login_port: \u0026#34;{{ mongodb_port }}\u0026#34; replica_set: \u0026#34;{{ mongodb_replset_name }}\u0026#34; members: \u0026#34;{{ mongodb_members }}\u0026#34; when: mongodb_rs_check.stdout | trim == \u0026#39;\u0026#39; run_once: true - name: Tunggu node pertama menjadi primary ansible.builtin.command: \u0026gt;- mongosh --quiet --port {{ mongodb_port }} --eval \u0026#34;db.hello().isWritablePrimary\u0026#34; register: mongodb_primary_check until: mongodb_primary_check.stdout | trim == \u0026#39;true\u0026#39; retries: 30 delay: 5 changed_when: false run_once: true run_once tanpa delegate_to akan jalan di host pertama pada play, yaitu mongo-01. Kebetulan itu juga anggota yang saya kasih prioritas 2 di mongodb_members.\nMembuat user admin # roles/mongodb/tasks/users.yml - name: Buat user admin community.mongodb.mongodb_user: login_host: localhost login_port: \u0026#34;{{ mongodb_port }}\u0026#34; login_user: \u0026#34;{{ mongodb_admin_user }}\u0026#34; login_password: \u0026#34;{{ mongodb_admin_password }}\u0026#34; create_for_localhost_exception: /root/.mongodb_admin_created replica_set: \u0026#34;{{ mongodb_replset_name }}\u0026#34; database: admin name: \u0026#34;{{ mongodb_admin_user }}\u0026#34; password: \u0026#34;{{ mongodb_admin_password }}\u0026#34; roles: - root update_password: on_create state: present run_once: true no_log: true Opsi create_for_localhost_exception membuat user pertama lewat localhost exception, lalu menulis file penanda. Di run berikutnya, modul akan login seperti biasa pakai login_user dan login_password.\nMenyatukan semuanya # roles/mongodb/tasks/main.yml - name: Install ansible.builtin.import_tasks: install.yml - name: Konfigurasi ansible.builtin.import_tasks: configure.yml - name: Terapkan perubahan konfigurasi sebelum lanjut ansible.builtin.meta: flush_handlers - name: Pastikan mongod aktif ansible.builtin.systemd_service: name: mongod state: started enabled: true - name: Replica set ansible.builtin.import_tasks: replicaset.yml - name: User ansible.builtin.import_tasks: users.yml # site.yml - name: Deploy MongoDB replica set hosts: mongodb become: true roles: - mongodb Tinggal jalankan:\nansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass Verifikasi mongosh \u0026#34;mongodb://mongo-01:27017,mongo-02:27017,mongo-03:27017/?replicaSet=rs0\u0026amp;authSource=admin\u0026#34; \\ -u admin -p --eval \u0026#34;rs.status().members.map(m =\u0026gt; m.name + \u0026#39; \u0026#39; + m.stateStr)\u0026#34; Kalau semuanya lancar, akan muncul satu PRIMARY dan dua SECONDARY. Setelah itu coba jalankan playbook sekali lagi. Semua task harusnya berstatus ok tanpa ada yang changed.\nBeberapa hal yang saya pelajari Jangan pernah commit keyfile atau password sebagai teks biasa. Pakai Ansible Vault (atau secret manager) dari hari pertama. Idempotensi itu harus dites, jangan cuma diasumsikan. Jalankan playbook dua kali di lingkungan uji dan pastikan run kedua bersih. Batasi akses jaringan. Arahkan bindIp ke interface privat saja, lalu buka port 27017 di firewall hanya untuk anggota replica set dan aplikasi yang memang perlu. Coba failover-nya. Matikan primary di lingkungan uji, lalu pastikan aplikasi masih bisa menulis setelah election selesai. Di tulisan berikutnya, saya bahas cara pasang Prometheus exporter dengan Ansible supaya cluster seperti ini bisa dipantau.\n","permalink":"https://rifkiaz.my.id/posts/ansible-mongodb-replica-set/","summary":"\u003cp\u003ePasang satu server MongoDB secara manual itu gampang. Tapi kalau harus pasang tiga node replica set dengan konfigurasi yang sama persis, keyfile yang sama, autentikasi aktif, dan bisa dijalankan ulang kapan saja tanpa merusak cluster yang sudah jalan, ceritanya jadi lain. Di tulisan ini saya mau berbagi cara saya mengotomasinya pakai Ansible.\u003c/p\u003e\n\u003cp\u003eOh iya, semua nama host, user, dan nilai di bawah cuma contoh, jadi sesuaikan saja dengan lingkunganmu.\u003c/p\u003e\n\u003ch2 id=\"yang-mau-dicapai\"\u003eYang mau dicapai\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eTiga node MongoDB (satu primary, dua secondary) dalam replica set \u003ccode\u003ers0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eAutentikasi internal antar-node menggunakan \u003cstrong\u003ekeyfile\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eauthorization\u003c/code\u003e aktif, dengan satu user admin.\u003c/li\u003e\n\u003cli\u003ePlaybook \u003cstrong\u003eidempoten\u003c/strong\u003e: mau dijalankan berapa kali pun hasilnya tetap sama, dan nggak mencoba \u003ccode\u003ers.initiate()\u003c/code\u003e lagi di cluster yang sudah hidup.\u003c/li\u003e\n\u003cli\u003eRahasia seperti password dan keyfile disimpan di \u003cstrong\u003eAnsible Vault\u003c/strong\u003e, bukan ditaruh di repository sebagai teks biasa.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"prasyarat\"\u003ePrasyarat\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eTiga server Ubuntu LTS yang bisa saling terhubung di port 27017 dan bisa dipanggil lewat nama host.\u003c/li\u003e\n\u003cli\u003eAnsible di mesin kontrol, ditambah collection \u003ccode\u003ecommunity.mongodb\u003c/code\u003e:\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eansible-galaxy collection install community.mongodb\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eModul-modul di collection ini butuh \u003ccode\u003epymongo\u003c/code\u003e di node target. Sebelum pasang, cek dulu versi yang didukung di dokumentasi collection-nya.\u003c/p\u003e","title":"Deploy MongoDB Replica Set dengan Ansible"},{"content":"Hampir semua orang ops punya satu folder berisi script kecil: cek disk, rotasi log, restart service, tarik laporan. Masalahnya baru kerasa waktu script itu mulai dipakai orang lain. Parameternya beda-beda, nggak ada mode uji coba, output-nya susah dibaca, dan satu salah ketik bisa langsung kena ke produksi.\nDi tulisan ini saya rangkum prinsip yang saya pakai untuk merapikan script-script seperti itu jadi satu automation tool internal yang bisa dipercaya tim.\nPrinsip desain 1. Satu pintu masuk, banyak subcommand Daripada belasan script dengan gaya masing-masing, satu CLI dengan beberapa subcommand jauh lebih gampang dipelajari:\nopsctl disk-report --host web-01 opsctl service restart nginx --host web-01 --dry-run opsctl backup verify --target db Cukup ingat satu nama, dan opsctl --help otomatis jadi dokumentasi yang selalu up to date.\n2. Dry-run untuk semua aksi yang mengubah sesuatu Setiap perintah yang mengubah sesuatu wajib punya --dry-run, yang menampilkan apa yang akan dilakukan tanpa benar-benar menjalankannya. Untuk aksi yang berisiko, minta konfirmasi dulu, dan sediakan --yes untuk dipakai di pipeline.\n3. Idempoten Menjalankan perintah yang sama dua kali harus tetap aman. Cek dulu kondisi sekarang, baru ubah kalau memang perlu. Prinsipnya sama seperti Ansible, dan alasannya juga sama: automation pasti bakal dijalankan ulang, entah karena retry, timeout, atau salah klik.\n4. Rahasia tidak pernah ada di kode Kredensial dibaca dari environment variable atau secret manager, jangan di-hardcode dan jangan di-commit. Tambahkan juga secret scanner di CI sebagai jaring pengaman.\n5. Output untuk manusia dan mesin Output default-nya ringkas dan enak dibaca, plus ada --output json supaya hasilnya bisa diolah tool lain. Log ke stderr, hasil ke stdout.\n6. Exit code yang jujur 0 berarti berhasil, selain itu gagal. Kedengarannya sepele, tapi justru ini yang bikin tool bisa dirangkai di cron, CI, atau tool otomasi lain.\nKerangka minimal dengan Python Ini contoh kerangkanya, cukup pakai library standar Python:\n#!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;opsctl: automation tool internal untuk tugas operasional.\u0026#34;\u0026#34;\u0026#34; import argparse import json import logging import shutil import sys log = logging.getLogger(\u0026#34;opsctl\u0026#34;) def disk_report(args): usage = shutil.disk_usage(args.path) percent = round(usage.used / usage.total * 100, 1) result = {\u0026#34;path\u0026#34;: args.path, \u0026#34;used_percent\u0026#34;: percent} if args.output == \u0026#34;json\u0026#34;: print(json.dumps(result)) else: print(f\u0026#34;{args.path}: {percent}% terpakai\u0026#34;) return 1 if percent \u0026gt;= args.threshold else 0 def clean_tmp(args): log.info(\u0026#34;Mencari file lama di %s\u0026#34;, args.path) # ... kumpulkan kandidat file yang akan dihapus ... candidates = [] for f in candidates: if args.dry_run: print(f\u0026#34;[dry-run] akan menghapus {f}\u0026#34;) else: log.info(\u0026#34;Menghapus %s\u0026#34;, f) # f.unlink() return 0 def main(): parser = argparse.ArgumentParser(prog=\u0026#34;opsctl\u0026#34;) parser.add_argument(\u0026#34;-v\u0026#34;, \u0026#34;--verbose\u0026#34;, action=\u0026#34;store_true\u0026#34;) parser.add_argument(\u0026#34;--output\u0026#34;, choices=[\u0026#34;text\u0026#34;, \u0026#34;json\u0026#34;], default=\u0026#34;text\u0026#34;) sub = parser.add_subparsers(dest=\u0026#34;command\u0026#34;, required=True) p = sub.add_parser(\u0026#34;disk-report\u0026#34;, help=\u0026#34;Laporan penggunaan disk\u0026#34;) p.add_argument(\u0026#34;--path\u0026#34;, default=\u0026#34;/\u0026#34;) p.add_argument(\u0026#34;--threshold\u0026#34;, type=float, default=90.0) p.set_defaults(func=disk_report) p = sub.add_parser(\u0026#34;clean-tmp\u0026#34;, help=\u0026#34;Bersihkan file sementara lama\u0026#34;) p.add_argument(\u0026#34;--path\u0026#34;, default=\u0026#34;/tmp\u0026#34;) p.add_argument(\u0026#34;--dry-run\u0026#34;, action=\u0026#34;store_true\u0026#34;) p.set_defaults(func=clean_tmp) args = parser.parse_args() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format=\u0026#34;%(levelname)s %(message)s\u0026#34;, stream=sys.stderr, ) sys.exit(args.func(args)) if __name__ == \u0026#34;__main__\u0026#34;: main() Kerangkanya sengaja dibuat sederhana. Setiap subcommand cuma fungsi biasa yang menerima args dan mengembalikan exit code, jadi gampang dites pakai pytest.\nStruktur repository opsctl/ ├── opsctl/ │ ├── __init__.py │ ├── cli.py │ └── commands/ │ ├── disk.py │ └── cleanup.py ├── tests/ ├── pyproject.toml └── README.md Kalau entry point-nya sudah didefinisikan di pyproject.toml, tool ini bisa dipasang pakai pipx install . dan langsung bisa dipanggil sebagai opsctl di laptop siapa pun di tim.\nKapan memakai Ansible, kapan memakai tool sendiri Ansible cocok untuk mengatur kondisi server: paket, konfigurasi, service. Contohnya bisa dilihat di deploy MongoDB replica set dengan Ansible. Tool internal cocok untuk tugas operasional yang sifatnya aksi atau laporan: cek cepat, bersih-bersih, integrasi dengan API internal, atau membungkus beberapa langkah yang sering dikerjakan barengan. Dua-duanya bisa saling melengkapi. Tool internal bahkan bisa memanggil playbook Ansible sebagai salah satu subcommand-nya.\nChecklist sebelum dibagikan ke tim Ada --help yang jelas di setiap subcommand Semua aksi yang mengubah state punya --dry-run Tidak ada secret di kode maupun riwayat git Ada test untuk logika inti CI menjalankan lint dan test di setiap pull request README berisi cara install dan contoh pemakaian Automation tool yang bagus nggak harus besar. Yang penting tim berani dan percaya untuk menjalankannya, dan rasa percaya itu dibangun dari hal-hal kecil di atas.\n","permalink":"https://rifkiaz.my.id/posts/automation-tools-internal/","summary":"\u003cp\u003eHampir semua orang ops punya satu folder berisi script kecil: cek disk, rotasi log, restart service, tarik laporan. Masalahnya baru kerasa waktu script itu mulai dipakai orang lain. Parameternya beda-beda, nggak ada mode uji coba, output-nya susah dibaca, dan satu salah ketik bisa langsung kena ke produksi.\u003c/p\u003e\n\u003cp\u003eDi tulisan ini saya rangkum prinsip yang saya pakai untuk merapikan script-script seperti itu jadi satu \u003cstrong\u003eautomation tool internal\u003c/strong\u003e yang bisa dipercaya tim.\u003c/p\u003e","title":"Membangun Automation Tools Internal untuk Pekerjaan Ops"},{"content":"Hampir semua tim teknis pernah begini: butuh berbagi potongan log, konfigurasi, atau stack trace dengan cepat, lalu langsung tempel ke pastebin publik. Praktis sih, tapi berisiko. Log sering berisi token, alamat internal, atau data pengguna. Begitu masuk ke layanan publik, kita sudah nggak bisa mengontrol siapa yang membacanya atau berapa lama datanya disimpan.\nJalan keluarnya adalah paste internal. Sama praktisnya, tapi jalan di infrastruktur sendiri dan cuma bisa diakses orang yang berhak. Di tulisan ini saya rangkum hal-hal yang perlu dipikirkan waktu mendesainnya, plus cara deploy yang menurut saya masuk akal.\nKebutuhan minimum Sebelum pilih tools, saya tulis dulu kebutuhannya:\nKebutuhan Alasan Cuma bisa diakses dari jaringan internal atau setelah login Supaya isinya nggak bocor ke luar Masa kedaluwarsa (expiry) per paste Data sensitif nggak numpuk selamanya Opsi burn after reading Pas untuk berbagi kredensial sementara yang sekali pakai Enkripsi, idealnya di sisi klien Admin server pun nggak bisa membaca isinya Syntax highlighting Log dan konfigurasi lebih mudah dibaca Batas ukuran dan rate limit Mencegah penyalahgunaan dan disk penuh Nggak diindeks mesin pencari Supaya paste nggak muncul di hasil pencarian Pilihan pendekatan Pakai aplikasi open source yang sudah matang. Salah satu yang populer adalah PrivateBin. Paste dienkripsi di browser sebelum dikirim ke server, dan kunci dekripsinya ada di bagian #fragment URL yang nggak pernah ikut terkirim ke server. Expiry, burn after reading, dan syntax highlighting juga sudah tersedia.\nBikin sendiri. Ini masuk akal kalau ada kebutuhan khusus, misalnya integrasi dengan SSO internal, audit log, atau API yang bisa dipanggil dari script. Konsekuensinya, pemeliharaan dan keamanannya jadi tanggung jawab kita sendiri.\nUntuk kebanyakan tim, saran saya mulai saja dari aplikasi yang sudah ada, lalu tambahkan autentikasi dan aturan main di depannya.\nArsitektur Pengguna ──HTTPS──▶ Reverse proxy (TLS + autentikasi) ──▶ Aplikasi paste ──▶ Storage Reverse proxy (Nginx, HAProxy, atau Caddy) mengurus TLS, autentikasi, rate limit, dan header keamanan. Aplikasi paste cuma listen di 127.0.0.1 atau jaringan privat, nggak pernah langsung menghadap internet. Storage untuk skala kecil cukup filesystem saja, atau database kalau butuh replikasi. Contoh deploy dengan Docker Compose # docker-compose.yml services: paste: image: privatebin/nginx-fpm-alpine:\u0026lt;versi-yang-di-pin\u0026gt; restart: unless-stopped read_only: true ports: - \u0026#34;127.0.0.1:8080:8080\u0026#34; volumes: - ./data:/srv/data - ./conf.php:/srv/cfg/conf.php:ro Beberapa catatan:\nKunci versi image, jangan pakai latest, supaya upgrade selalu disengaja dan gampang di-rollback. read_only: true bikin filesystem container nggak bisa ditulis, kecuali volume data. Port cuma di-bind ke 127.0.0.1, jadi satu-satunya pintu masuk ya lewat reverse proxy. Pastikan direktori ./data bisa ditulis oleh user yang dipakai container (cek dokumentasi image-nya). Di conf.php, atur nilai default yang aman, misalnya expiry bawaan yang pendek dan batas ukuran paste yang wajar.\nReverse proxy dan autentikasi Contoh paling sederhana pakai Nginx:\nserver { listen 443 ssl; server_name paste.example.com; ssl_certificate /etc/ssl/certs/paste.crt; ssl_certificate_key /etc/ssl/private/paste.key; # Contoh paling sederhana. Untuk tim besar, ganti dengan SSO/OIDC. auth_basic \u0026#34;Internal\u0026#34;; auth_basic_user_file /etc/nginx/.htpasswd; add_header X-Robots-Tag \u0026#34;noindex, nofollow\u0026#34; always; add_header Referrer-Policy \u0026#34;no-referrer\u0026#34; always; client_max_body_size 2m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } Header Referrer-Policy: no-referrer ini penting. Tanpa header ini, URL paste lengkap bisa ikut terkirim sebagai referrer waktu ada yang mengklik link di dalam paste.\nKalau organisasimu sudah punya identity provider, lebih baik autentikasinya diserahkan ke SSO lewat proxy seperti oauth2-proxy. Jadi waktu akun seseorang dinonaktifkan, aksesnya ikut tercabut otomatis.\nOperasional Backup: tanya dulu, paste ini memang perlu di-backup? Untuk data yang sifatnya sementara, jawabannya sering kali nggak. Pembersihan: pastikan paste yang sudah kedaluwarsa benar-benar terhapus dari storage, bukan cuma disembunyikan. Monitoring: pantau pemakaian disk dan error rate di reverse proxy. Update: rutin cek rilis keamanan aplikasi dan base image-nya. Yang paling penting: kebiasaan tim Tools cuma separuh solusi. Separuhnya lagi adalah membiasakan tim pakai paste internal dan nggak balik lagi ke pastebin publik. Yang biasanya membantu: URL yang gampang diingat, tampilan yang nggak kalah nyaman dari layanan publik, dan pengingat singkat di dokumentasi onboarding.\n","permalink":"https://rifkiaz.my.id/posts/paste-internal-yang-aman/","summary":"\u003cp\u003eHampir semua tim teknis pernah begini: butuh berbagi potongan log, konfigurasi, atau stack trace dengan cepat, lalu langsung tempel ke pastebin publik. Praktis sih, tapi berisiko. Log sering berisi token, alamat internal, atau data pengguna. Begitu masuk ke layanan publik, kita sudah nggak bisa mengontrol siapa yang membacanya atau berapa lama datanya disimpan.\u003c/p\u003e\n\u003cp\u003eJalan keluarnya adalah \u003cstrong\u003epaste internal\u003c/strong\u003e. Sama praktisnya, tapi jalan di infrastruktur sendiri dan cuma bisa diakses orang yang berhak. Di tulisan ini saya rangkum hal-hal yang perlu dipikirkan waktu mendesainnya, plus cara deploy yang menurut saya masuk akal.\u003c/p\u003e","title":"Membangun Layanan Paste Internal yang Aman"},{"content":"Day 0 My day 0 is a travel to Malaysia, and this is first time for me. because I will try solo traveling to another country, and for trial I try going to Malaysia. My flight with Air Asia depart from Bandara International Soekarno Hatta to Kuala Lumpur. After landing on Kuala Lumpur International Airport at 2 AM. I ordered Grab Car from Airport to My Hotel. After arrived at hotel I am taking a rest for the summit.\nDay 1 In day 1, I order grab car from hotel to the venue. I see some people who have been on Twitter like Kristi, Anisa Kuchi and others. In day 1 many good speaker have a perform. I interested when Kristi and Anisa Kuci talks about GNOME and Community. Because I love to contribute opensource community On afternoon, We lunch at pasar to try special food from Malaysia. For Me Indonesia and Malaysia food is same. After lunch We try a Soya, special food from Malaysia After Day 1, I walked to my hotel Day 2 Day 2 is a special day for Me, why? because My talk is on day 2. And for My talk I have brought My Keyboard for flexing to others :p In My talk, I Talk about My Journey with GNOME, openSUSE and My Mechanical Keyboard. And some people interested with My Talk. And some people try My keyboard like Matthias. After My Talk, We lunch at restaurant santai at Kuala Lumpur, and I try Nasi Ambeng For GNOME.Asia on this year, many students have join GSOC present about they project and it\u0026rsquo;s awesome. Final talk is Syazwan talk about Him Journey with Open Source. Day 3 On day 3, I not join 1 day tour, because I have a plan on Malaysia :D. Thanks to: Our photos can be access via Google Photo Groups: https://photos.app.goo.gl/bXjSkXQhTuhtTZhKA\n","permalink":"https://rifkiaz.my.id/posts/gnomeasia/","summary":"\u003ch1 id=\"day-0\"\u003eDay 0\u003c/h1\u003e\n\u003cp\u003eMy day 0 is a travel to Malaysia, and this is first time for me. because I will try solo traveling to another country, and for trial I try going to Malaysia.\nMy flight with Air Asia depart from Bandara International Soekarno Hatta to Kuala Lumpur.\n\u003cimg alt=\"on cabin Air Asia\" loading=\"lazy\" src=\"/posts/gnomeasia/assets/day0-1.webp\" title=\"In Cabin Air Asia\"\u003e\nAfter landing on Kuala Lumpur International Airport at 2 AM.\n\u003cimg alt=\"Breakfast at KL\" loading=\"lazy\" src=\"/posts/gnomeasia/assets/breakfast_my.webp\" title=\"Breakfast afer arrived at KL\"\u003e\nI ordered Grab Car from Airport to My \u003ca href=\"https://goo.gl/maps/R6gA3KfagmZ5G64o7\"\u003eHotel\u003c/a\u003e. After arrived at \u003ca href=\"https://goo.gl/maps/R6gA3KfagmZ5G64o7\"\u003ehotel\u003c/a\u003e I am taking a rest for the summit.\u003c/p\u003e","title":"My Journey with GNOME Asia Summit 2022 Malaysia"},{"content":"Ketika selesai menginstall git di openSUSE, ketika akan melakukan push ataupun pull pada repository yang membutuhkan autentikasi, akan muncul pop up untuk mengisi kolom nama dan password, Hal tersebut bagi saya kurang nyaman, maka bisa dinonaktifkan dengan cara berikut :\ngit config --global core.askpass \u0026#39;\u0026#39; Sekian~.\n","permalink":"https://rifkiaz.my.id/posts/disablesshaskpask/","summary":"\u003cp\u003eKetika selesai menginstall git di openSUSE, ketika akan melakukan push ataupun pull pada repository yang membutuhkan autentikasi, akan muncul pop up untuk mengisi kolom nama dan password, Hal tersebut bagi saya kurang nyaman, maka bisa dinonaktifkan dengan cara berikut :\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit config --global core.askpass \u0026#39;\u0026#39; \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSekian~.\u003c/p\u003e","title":"Disable SSH Ask Pass di openSUSE"},{"content":"Pendahuluan Container, ya sebuah istilah yang sedang populer pada era saat ini. dan saya pun memakainya baik untuk development aplikasi ataupun production. Namun beberapa hari belakangan ini tim saya mengalami kendala yakni sekitar 1-2 hari semua container yang berada di VM production berhenti secara tiba. Setelah diteliti hal ini disebabkan oleh docker daemon yang restart tanpa disuruh (asli bikin kesel). Dan kesalahan dari tim DevOps ialah tidak menambahkan parameter restart: always ketika production, jadilah ketika daemon docker restart dan container tidak me-restart otomatis. Yang lebih gak enak lagi ialah para client yang lebih dulu tau ketimbang tim DevOps. Karena itu pada tulisan kali ini saya akan membagi sedikit tips yang dibantu oleh Kang Mustapha untuk monitoring container docker dan mengirimkan alert ke Telegram apabila ada container yang mati (exited).\nKebutuhan Docker Bot Telegram Cron Tahapan Siapkan bot Telegram untuk mengirimkan pesan alert ke tim. Untuk panduannya bisa dilihat di link Buat skrip alert, vim script-alert.sh ............ #!/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin export IPVM=$(/sbin/ifconfig ens3 | grep -i mask | awk \u0026#39;{print $2}\u0026#39;) vm=$(hostname) list_of_containers=$(sudo docker ps -a | awk \u0026#39;{if(NR\u0026gt;1) print $NF}\u0026#39;) containers=`docker ps -f status=running --format \u0026#34;{{.Names}}\u0026#34;` for i in $list_of_containers do if echo $containers |grep $i then echo \u0026#34;$i online \u0026#34; else curl -X POST -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#39;{\u0026#34;chat_id\u0026#34;: \u0026#34;[CHATID]\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;\u0026#39;\u0026#34;container $i on $vm($IPVM) exited\u0026#34;\u0026#39;\u0026#34;, \u0026#34;disable_notification\u0026#34;: true}\u0026#39; https://api.telegram.org/bot[YOUR_BOT_TOKEN]/sendMessage \u0026gt; /dev/null fi done exit 0 Buat cron dan jadwalkan diperiksan tiap satu menit crontab -e */1 * * * * /bin/sh /home/ubuntu/script-alert.sh Apabila ada container yang mati maka Bot akan mengirimkan alert seperti beritku Voila, docker alert berhasil dibuat.\nSekian, semoga membantu\n","permalink":"https://rifkiaz.my.id/posts/buatdockeralert/","summary":"\u003ch3 id=\"pendahuluan\"\u003ePendahuluan\u003c/h3\u003e\n\u003cp\u003eContainer, ya sebuah istilah yang sedang populer pada era saat ini. dan saya pun memakainya baik untuk development aplikasi ataupun production. Namun beberapa hari belakangan ini tim saya mengalami kendala yakni sekitar 1-2 hari semua container yang berada di VM production berhenti secara tiba. Setelah diteliti hal ini disebabkan oleh docker daemon yang restart tanpa disuruh (asli bikin kesel). Dan kesalahan dari tim DevOps ialah tidak menambahkan parameter restart: always ketika production, jadilah ketika daemon docker restart dan container tidak me-restart otomatis. Yang lebih gak enak lagi ialah para client yang lebih dulu tau ketimbang tim DevOps. Karena itu pada tulisan kali ini saya akan membagi sedikit tips yang dibantu oleh Kang \u003ca href=\"https://instagram.com/topahadzi\"\u003eMustapha\u003c/a\u003e untuk monitoring container docker dan mengirimkan alert ke Telegram apabila ada container yang mati (exited).\u003c/p\u003e","title":"Membuat Docker Alert"},{"content":"POWERDNS adalah DNS Server berbasis MySQL, yang ditulis dalam C ++ dan berlisensi Under GPL. PowerDNS dapat dikelola melalui web base interface (PowerAdmin). Pada tutorial kali ini akan membahas bagaimana menginstall dan mengkonfigurasi POWERDNS pada Ubuntu 18.04.\nPersiapan Ubuntu 18.04 Domain Tahap Instalasi Update repositori pada Ubuntu 18.04 sudo apt update Install MariaDB sudo apt install mariadb-server mariadb-client -y mysql_secure_installation Edit konfigurasi pada MariaDB vim /etc/mysql/mariadb.conf/50-server.cnf ........ bind = 0.0.0.0 ----\u0026gt; ganti dengan ip lokal ........ Restart MariaDB sudo systemctl restart mariadb Buat database pada database sudo mysql -u root -p CREATE DATABASE powerdns CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; GRANT ALL ON powerdns.* TO \u0026#39;powerdns\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;YOURPASSWORD\u0026#39;; GRANT ALL ON powerdns.* TO \u0026#39;powerdns\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;YOURPASSWORD\u0026#39;; FLUSH PRIVILEGES; USE powerdns; CREATE TABLE domains ( id INT AUTO_INCREMENT, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INT DEFAULT NULL, type VARCHAR(6) NOT NULL, notified_serial INT DEFAULT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE UNIQUE INDEX name_index ON domains(name); CREATE TABLE records ( id BIGINT AUTO_INCREMENT, domain_id INT DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, type VARCHAR(10) DEFAULT NULL, content VARCHAR(64000) DEFAULT NULL, ttl INT DEFAULT NULL, prio INT DEFAULT NULL, change_date INT DEFAULT NULL, disabled TINYINT(1) DEFAULT 0, ordername VARCHAR(255) BINARY DEFAULT NULL, auth TINYINT(1) DEFAULT 1, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX nametype_index ON records(name,type); CREATE INDEX domain_id ON records(domain_id); CREATE INDEX ordername ON records (ordername); CREATE TABLE supermasters ( ip VARCHAR(64) NOT NULL, nameserver VARCHAR(255) NOT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL, PRIMARY KEY (ip, nameserver) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE TABLE comments ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(10) NOT NULL, modified_at INT NOT NULL, account VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL, comment TEXT CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX comments_name_type_idx ON comments (name, type); CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); CREATE TABLE domainmetadata ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, kind VARCHAR(32), content TEXT, PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); CREATE TABLE cryptokeys ( id INT AUTO_INCREMENT, domain_id INT NOT NULL, flags INT NOT NULL, active BOOL, content TEXT, PRIMARY KEY(id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE INDEX domainidindex ON cryptokeys(domain_id); CREATE TABLE tsigkeys ( id INT AUTO_INCREMENT, name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), PRIMARY KEY (id) ) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;; CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); Disable systemd-resolved sudo su systemctl disable systemd-resolved systemctl stop systemd-resolved ls -lh /etc/resolv.conf rm /etc/resolv.conf echo \u0026#34;nameserver 8.8.8.8\u0026#34; \u0026gt; /etc/resolv.conf Install POWERDNS (versi 4.1.13) sudo su edit repositori vi /etc/apt/sources.list.d/pdns.list ............. deb [arch=amd64] http://repo.powerdns.com/ubuntu bionic-auth-41 main ............. vi /etc/apt/preferences.d/pdns ... Package: pdns-* Pin: origin repo.powerdns.com Pin-Priority: 600 ... Install powerdns beserta dependensi yang dibutuhkan curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo \u0026#34;deb https://dl.yarnpkg.com/debian/ stable main\u0026#34; | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update sudo apt-get install pdns-server pdns-backend-mysql virtualenv python3-pip yarn -y apt-get install -y libmysqlclient-dev python3-mysqldb python-mysqldb libsasl2-dev libffi-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev pkg-config Setelah berhasil terinstall konfigurasi powerdns agar terhubung dengan backend mysql sudo vi /etc/powerdns/pdns.d/pdns.local.gmysql.conf ## Masukkan skrip berikut tanpa tanda # ...... launch=gmysql gmysql-host=#IPHOSTMYSQL gmysql-user=#USER gmysql-dbname=#YOURDB gmysql-password=#YOURPASSWORD gmysql-dnssec=yes Konfigurasi pdns.conf agar bisa dimonitoring echo \u0026#39;api=yes\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;api-key=YOURIPKEY\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;webserver=yes\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;webserver-address=0.0.0.0\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;webserver-allow-from=0.0.0.0/0,::/0\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;webserver-port=8081\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;version-string=anonymous\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf echo \u0026#39;webserver-password=YOURPASSWORD\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf Tes koneksi ke database /usr/sbin/pdns_server --daemon=no --guardian=no --loglevel=9 Jika berhasil restart pdns service sudo systemctl restart pdns Untuk tampilan GUI (POWERDNS Admin) di postingan selanjutnya ya! Salam Kebebasan!! Bye~\n","permalink":"https://rifkiaz.my.id/posts/install-powerdns-di-ubuntu-18.04/","summary":"\u003cp\u003e\u003ca href=\"https://www.powerdns.com/\"\u003ePOWERDNS\u003c/a\u003e adalah DNS Server berbasis MySQL, yang ditulis dalam C ++ dan berlisensi Under GPL. PowerDNS dapat dikelola melalui web base interface (PowerAdmin). Pada tutorial kali ini akan membahas bagaimana menginstall dan mengkonfigurasi \u003ca href=\"https://www.powerdns.com\"\u003ePOWERDNS\u003c/a\u003e pada \u003ca href=\"http://kambing.ui.ac.id/iso/ubuntu/releases/bionic/\"\u003eUbuntu 18.04\u003c/a\u003e.\u003c/p\u003e\n\u003ch3 id=\"persiapan\"\u003ePersiapan\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"http://kambing.ui.ac.id/iso/ubuntu/releases/bionic/\"\u003eUbuntu 18.04\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eDomain\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"tahap-instalasi\"\u003eTahap Instalasi\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eUpdate repositori pada Ubuntu 18.04\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt update \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eInstall MariaDB\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install mariadb-server mariadb-client -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emysql_secure_installation\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eEdit konfigurasi pada MariaDB\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evim /etc/mysql/mariadb.conf/50-server.cnf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e........\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebind = 0.0.0.0 ----\u0026gt; ganti dengan ip lokal\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e........\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eRestart MariaDB\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo systemctl restart mariadb\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eBuat database pada database\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo mysql -u root -p \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE DATABASE powerdns CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eGRANT ALL ON powerdns.* TO \u0026#39;powerdns\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;YOURPASSWORD\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eGRANT ALL ON powerdns.* TO \u0026#39;powerdns\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;YOURPASSWORD\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eFLUSH PRIVILEGES;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUSE powerdns; \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE domains (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    INT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ename                  VARCHAR(255) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emaster                VARCHAR(128) DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elast_check            INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etype                  VARCHAR(6) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enotified_serial       INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaccount               VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE UNIQUE INDEX name_index ON domains(name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE records (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    BIGINT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edomain_id             INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ename                  VARCHAR(255) DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etype                  VARCHAR(10) DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003econtent               VARCHAR(64000) DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ettl                   INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eprio                  INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echange_date           INT DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edisabled              TINYINT(1) DEFAULT 0,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eordername             VARCHAR(255) BINARY DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eauth                  TINYINT(1) DEFAULT 1,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX nametype_index ON records(name,type);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX domain_id ON records(domain_id);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX ordername ON records (ordername);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE supermasters (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eip                    VARCHAR(64) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enameserver            VARCHAR(255) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaccount               VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (ip, nameserver)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE comments (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    INT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edomain_id             INT NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ename                  VARCHAR(255) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etype                  VARCHAR(10) NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emodified_at           INT NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaccount               VARCHAR(40) CHARACTER SET \u0026#39;utf8\u0026#39; DEFAULT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecomment               TEXT CHARACTER SET \u0026#39;utf8\u0026#39; NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX comments_name_type_idx ON comments (name, type);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX comments_order_idx ON comments (domain_id, modified_at);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE domainmetadata (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    INT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edomain_id             INT NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ekind                  VARCHAR(32),\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003econtent               TEXT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE cryptokeys (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    INT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edomain_id             INT NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eflags                 INT NOT NULL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eactive                BOOL,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003econtent               TEXT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY(id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE INDEX domainidindex ON cryptokeys(domain_id);\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE TABLE tsigkeys (\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eid                    INT AUTO_INCREMENT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ename                  VARCHAR(255),\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ealgorithm             VARCHAR(50),\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esecret                VARCHAR(255),\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePRIMARY KEY (id)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e) Engine=InnoDB CHARACTER SET \u0026#39;latin1\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eDisable systemd-resolved\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo su\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl disable systemd-resolved\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl stop systemd-resolved\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003els -lh /etc/resolv.conf \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003erm /etc/resolv.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#34;nameserver 8.8.8.8\u0026#34; \u0026gt; /etc/resolv.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eInstall POWERDNS (versi 4.1.13)\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo su\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eedit repositori\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evi /etc/apt/sources.list.d/pdns.list\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.............\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edeb [arch=amd64] http://repo.powerdns.com/ubuntu bionic-auth-41 main\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.............\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evi /etc/apt/preferences.d/pdns\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePackage: pdns-*\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePin: origin repo.powerdns.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePin-Priority: 600\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eInstall powerdns beserta dependensi yang dibutuhkan\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add - \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#34;deb https://dl.yarnpkg.com/debian/ stable main\u0026#34; | sudo tee /etc/apt/sources.list.d/yarn.list\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt-get update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt-get install pdns-server pdns-backend-mysql virtualenv python3-pip yarn -y\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eapt-get install -y libmysqlclient-dev python3-mysqldb python-mysqldb libsasl2-dev libffi-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev pkg-config\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eSetelah berhasil terinstall konfigurasi powerdns agar terhubung dengan backend mysql\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo vi /etc/powerdns/pdns.d/pdns.local.gmysql.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e## Masukkan skrip berikut tanpa tanda #\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e......\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elaunch=gmysql\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egmysql-host=#IPHOSTMYSQL\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egmysql-user=#USER\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egmysql-dbname=#YOURDB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egmysql-password=#YOURPASSWORD\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egmysql-dnssec=yes\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eKonfigurasi pdns.conf agar bisa dimonitoring\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;api=yes\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;api-key=YOURIPKEY\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;webserver=yes\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;webserver-address=0.0.0.0\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;webserver-allow-from=0.0.0.0/0,::/0\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;webserver-port=8081\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;version-string=anonymous\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eecho \u0026#39;webserver-password=YOURPASSWORD\u0026#39; \u0026gt;\u0026gt; /etc/powerdns/pdns.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eTes koneksi ke database\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e/usr/sbin/pdns_server --daemon=no --guardian=no --loglevel=9\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eJika berhasil restart pdns service\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo systemctl restart pdns\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"untuk-tampilan-gui-powerdns-admin-di-postingan-selanjutnya-ya\"\u003eUntuk tampilan GUI (POWERDNS Admin) di postingan selanjutnya ya!\u003c/h3\u003e\n\u003cp\u003eSalam Kebebasan!!\nBye~\u003c/p\u003e","title":"Install POWERDNS di Ubuntu 18.04"},{"content":"OPNsense adalah proyek firewall berbasis open source berbasis FreeBSD OPNsense.\nBerikut adalah tutorial untuk mengkonfigurasi HAProxy di OPNsense.\n1. Konfigurasi Firewall untuk HAProxy Buat 2 IP Forward pada konfigurasi firewall Firewall \u0026gt; NAT \u0026gt; Port Forward \u0026gt; + New Rule Interface : LAN TCP/IP Ver : IPv4 Protocol : TCP Source : any Destination port range : From - HTTP , to - HTTP Redirect target IP : #IPADDRESSOPNSENSE Redirect target port : HTTP SAVE Interface : LAN TCP/IP Ver : IPv4 Protocol : TCP Source : any Destination port range : From - HTTPS , to - HTTPS Redirect target IP : #IPADDRESSOPNSENSE Redirect target port : HTTP SAVE Buat Rule Floating Firewall \u0026gt; Rules Floating \u0026gt; + New Rule Action : Pass Interface : LAN Direction : in TCP/IP ver : IPv4 Protocol : TCP Source : any Destination : Lan Address Destination port range : From - HTTP , to - HTTP Action : Pass Interface : LAN Direction : in TCP/IP ver : IPv4 Protocol : TCP Source : any Destination : Lan Address Destination port range : From - HTTPS , to - HTTPS 2. Konfigurasi HAProxy Service \u0026gt; HAProxy \u0026gt; Setting Tambahkan Server Real Servers \u0026gt; Real Servers \u0026gt; + add Name : test-http FQDN or IP : #IPPUBLICSERVER Port : 80 SAVE Tambahkan Backend Pool Virtual Services \u0026gt; Backend Pool \u0026gt; + add Name : test_http Mode : Layer 7 Servers : test-http SAVE Tambahkan Conditions Rules and Check \u0026gt; Conditions \u0026gt; + add Name: test-http Condition type : Host Matches Host String : test-http Tambahkan Rules untuk frontend 443 Rules and Check \u0026gt; Rules \u0026gt; + add Name : pass_https_test_http Select Condition : test-http Execute function : Use specified Backend Pool Use backend pool : test_http Tambahkan Rules untuk frontend 80 (untuk redirect traffic ke https) Rules and Check \u0026gt; Rules \u0026gt; + add Name : redirect_to_https_http_test_nicolas_my_id Select Condition : test-http_nicolas_my_id Execute function : http-request redirect HTTP Redirect : scheme https Tambahkan frontend untuk port 80 Virtual Services \u0026gt; Public Services \u0026gt; + add Name : FrontEndHTTP Listent address : 0.0.0.0:80 Select Rules : redirect_to_https_http Tambahkan frontend untuk port 443 Virtual Services \u0026gt; Public Services \u0026gt; + add Name : FrontEndHTTPS Listent address : 0.0.0.0:443 Enable SSL offloading : (✓) Certificates : test-http # Pastikan sertifikat sudah di-import ke OPNsense Select Rules : pass_https_test_http ","permalink":"https://rifkiaz.my.id/posts/setting-konfigurasi-haproxy-di-opnsense/","summary":"\u003cp\u003eOPNsense adalah proyek firewall berbasis open source berbasis FreeBSD \u003ca href=\"https://opnsense.org/\"\u003eOPNsense\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"OPNsense dashboard\" loading=\"lazy\" src=\"/posts/setting-konfigurasi-haproxy-di-opnsense/assets/dashboard.webp\"\u003e\u003c/p\u003e\n\u003cp\u003eBerikut adalah tutorial untuk mengkonfigurasi HAProxy di OPNsense.\u003c/p\u003e\n\u003ch3 id=\"1-konfigurasi-firewall-untuk-haproxy\"\u003e1. Konfigurasi Firewall untuk HAProxy\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eBuat 2 IP Forward pada konfigurasi firewall\n\u003cul\u003e\n\u003cli\u003eFirewall \u0026gt; NAT \u0026gt; Port Forward \u0026gt; + New Rule\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eInterface : LAN\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCP/IP Ver : IPv4\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eProtocol : TCP\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSource : any\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination port range : From - HTTP , to - HTTP \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRedirect target IP : #IPADDRESSOPNSENSE\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRedirect target port : HTTP\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSAVE\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eInterface : LAN\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCP/IP Ver : IPv4\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eProtocol : TCP\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSource : any\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination port range : From - HTTPS , to - HTTPS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRedirect target IP : #IPADDRESSOPNSENSE\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRedirect target port : HTTP\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSAVE\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003eBuat Rule Floating\n\u003cul\u003e\n\u003cli\u003eFirewall \u0026gt; Rules Floating \u0026gt; + New Rule\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAction : Pass\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eInterface : LAN\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDirection : in \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCP/IP ver : IPv4\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eProtocol : TCP \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSource : any \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination : Lan Address\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination port range : From - HTTP , to - HTTP \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAction : Pass\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eInterface : LAN\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDirection : in \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCP/IP ver : IPv4\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eProtocol : TCP \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSource : any \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination : Lan Address\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDestination port range : From - HTTPS , to - HTTPS\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"2-konfigurasi-haproxy\"\u003e2. Konfigurasi HAProxy\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eService \u0026gt; HAProxy \u0026gt; Setting\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"tambahkan-server\"\u003eTambahkan Server\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eReal Servers \u0026gt; Real Servers \u0026gt; + add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName : test-http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eFQDN or IP  : #IPPUBLICSERVER\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePort : 80\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSAVE\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-backend-pool\"\u003eTambahkan Backend Pool\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eVirtual Services \u0026gt; Backend Pool \u0026gt; + add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName : test_http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eMode : Layer 7 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eServers : test-http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSAVE\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-conditions\"\u003eTambahkan Conditions\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRules and Check \u0026gt; Conditions \u0026gt; + add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName: test-http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCondition type : Host Matches\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHost String : test-http\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-rules-untuk-frontend-443\"\u003eTambahkan Rules untuk frontend 443\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRules and Check \u0026gt; Rules \u0026gt; + add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName : pass_https_test_http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSelect Condition : test-http\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eExecute function : Use specified Backend Pool\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUse backend pool : test_http\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-rules-untuk-frontend-80-untuk-redirect-traffic-ke-https\"\u003eTambahkan Rules untuk frontend 80 (untuk redirect traffic ke https)\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRules and Check \u0026gt; Rules \u0026gt; + add \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName : redirect_to_https_http_test_nicolas_my_id\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSelect Condition : test-http_nicolas_my_id\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eExecute function : http-request redirect\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHTTP Redirect  : scheme https\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-frontend-untuk-port-80\"\u003eTambahkan frontend untuk port 80\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eVirtual Services \u0026gt; Public Services \u0026gt; + add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eName : FrontEndHTTP\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eListent address : 0.0.0.0:80\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSelect Rules : redirect_to_https_http\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"tambahkan-frontend-untuk-port-443\"\u003eTambahkan frontend untuk port 443\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eVirtual\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003ePublic\u003c/span\u003e \u003cspan class=\"n\"\u003eServices\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eadd\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eName\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eFrontEndHTTPS\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eListent\u003c/span\u003e \u003cspan class=\"n\"\u003eaddress\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.0\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.0\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e443\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eEnable\u003c/span\u003e \u003cspan class=\"n\"\u003eSSL\u003c/span\u003e \u003cspan class=\"n\"\u003eoffloading\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e✓\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eCertificates\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003etest\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ehttp\u003c/span\u003e \u003cspan class=\"c1\"\u003e# Pastikan sertifikat sudah di-import ke OPNsense\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSelect\u003c/span\u003e \u003cspan class=\"n\"\u003eRules\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epass_https_test_http\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"Setting Konfigurasi HAProxy Di OPNsense"},{"content":"Kenapa Hugo? Pertanyaan ini pasti muncul ketika melihat tulisan ini, kenapa mesti Hugo? apa hebatnya? kn ada wordpress dan blogger dan lain - lain, okelah jika dibandingkan dengan CMS tersebut web statis seperti Hugo ini tidak kalah powerful, tapi saya punya beberapa kendala ketika menggunakan wordpress, antara lain :\nSize storage di hosting saya sangat kecil (maklum belom jadi sultan), sehingga ketika mau upload gambar mesti mikir - mikir dulu beberapa parameter seperti size dan lain - lain. Kalau tidak ada Internet mau kustomisasi blog susah, ya walaupun kadang tinggal install LAMPP atau XAMPP si, tapi menurut saya hal tersebut mulai agak ribet. Hugo ini praktis, dulu pernah coba Jekyll tapi ya rada nganu, panjang kalo diceritain. Hugo ini enak, deploy cepet, gampang dipelajarin pula, walau belum bisa bahasa GO pun. Jika dikolaborasikan dengan GitHub Pages Hugo ini menyenangkan, build otomatis pake travis CI atau tools lain. pokoknya tinggal add -\u0026gt; commit -\u0026gt; push -\u0026gt; ngopi -\u0026gt; finish. Menulis konten di Hugo menggunakan MD (Markdown) yang mana format MD ini gampang banget dipelajarin, dokumentasi lengkap, dan tutorial banyak pula. Oke lanjut tutorial buat web dengan Hugo.\nMembuat Web Statis dengan Hugo Masukkan perintah berikut untuk membuat nama blog, masukkan [nama-blog] sesuai dengan nama yang Anda inginkan.\nhugo new site [nama-blog] Disini dicontohkan dengan nama blog \u0026rsquo;exampleblog'\nhugo new site exampleblog Maka output yang akan dikeluarkan seperti berikut : Setelah membuat blog dengan perintah tersebut maka beberapa direktori yang dibuat seperti gambar berikut: Setelah berhasil membuat website \u0026ldquo;exampleblog\u0026rdquo; jalankan perintah berikut untuk melihat apakah hugo berhasil berjalan atau tidak\nhugo server -D Pasti akan tampil halaman kosong tanpa ada apapun, yak benar hal tersebut karena tema yang belum diterapkan pada websitenya. Pantau terus blog ini karena akan bersambung ke part 2 Terima kasih.\n","permalink":"https://rifkiaz.my.id/posts/membuat-blog-sederhana-dengan-hugo/","summary":"\u003ch2 id=\"kenapa-hugo\"\u003eKenapa Hugo?\u003c/h2\u003e\n\u003cp\u003ePertanyaan ini pasti muncul ketika melihat tulisan ini, kenapa mesti Hugo? apa hebatnya? kn ada wordpress dan blogger dan lain - lain, okelah jika dibandingkan dengan CMS tersebut web statis seperti Hugo ini tidak kalah powerful, tapi saya punya beberapa kendala ketika menggunakan wordpress, antara lain :\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSize storage di hosting saya sangat kecil (maklum belom jadi sultan), sehingga ketika mau upload gambar mesti mikir - mikir dulu beberapa parameter seperti size dan lain - lain.\u003c/li\u003e\n\u003cli\u003eKalau tidak ada Internet mau kustomisasi blog susah, ya walaupun kadang tinggal install LAMPP atau XAMPP si, tapi menurut saya hal tersebut mulai agak ribet.\u003c/li\u003e\n\u003cli\u003eHugo ini praktis, dulu pernah coba Jekyll tapi ya rada nganu, panjang kalo diceritain. Hugo ini enak, deploy cepet, gampang dipelajarin pula, walau belum bisa bahasa GO pun.\u003c/li\u003e\n\u003cli\u003eJika dikolaborasikan dengan GitHub Pages Hugo ini menyenangkan, build otomatis pake travis CI atau tools lain. pokoknya tinggal add -\u0026gt; commit -\u0026gt; push -\u0026gt; ngopi -\u0026gt; finish.\u003c/li\u003e\n\u003cli\u003eMenulis konten di Hugo menggunakan MD (Markdown) yang mana format MD ini gampang banget dipelajarin, dokumentasi lengkap, dan tutorial banyak pula.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eOke lanjut tutorial buat web dengan Hugo.\u003c/p\u003e","title":"Membuat Blog Sederhana Dengan Hugo"},{"content":"Pengenalan Hugo merupakan sebuah generator web statis seperti Hexo, Hugo ditulis menggunakan bahasa Golang sehingga load page lebih cepat dibanding dengan Hexo. Oh ya kebetulan website ini menggunakan Hugo loh.\nInstall dependensi Sebelum menginstall Hugo pada openSUSE Tumbeleweed yang harus dilakukan adalah menginstall beberapa dependensi diantaranya adalah:\nGo Git Hugo sudo zypper in go git wget -c https://github.com/gohugoio/hugo/releases/download/v0.71.0/hugo_0.71.0_Linux-64bit.tar.gz tar -xvf hugo_0.71.0_Linux-64bit.tar.gz sudo mv hugo /usr/local/bin Cek Versi Hugo hugo version Jika sudah berhasil terinstall akan menampilkan output seperti berikut :\nHugo Static Site Generator v0.71.0-06150C87 linux/amd64 BuildDate: 2020-05-18T16:08:02Z Selesai ~\nNantikan untuk update selanjutnya perihal konfigurasi Hugo dan command - command sederhana untuk membuat post dan menerapkan tema ya ~\nSalam~.\nRefrensi: Hugo Documentation\n","permalink":"https://rifkiaz.my.id/posts/install-hugo-di-opensuse-tumbleweed/","summary":"\u003ch3 id=\"pengenalan\"\u003ePengenalan\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://gohugo.io\"\u003eHugo\u003c/a\u003e merupakan sebuah generator web statis seperti \u003ca href=\"https://hexo.io/\"\u003eHexo\u003c/a\u003e, \u003ca href=\"https://gohugo.io\"\u003eHugo\u003c/a\u003e ditulis menggunakan bahasa \u003ca href=\"https://golang.org/\"\u003eGolang\u003c/a\u003e sehingga load page lebih cepat dibanding dengan \u003ca href=\"https://hexo.io\"\u003eHexo\u003c/a\u003e. Oh ya kebetulan website ini menggunakan Hugo loh.\u003c/p\u003e\n\u003ch3 id=\"install-dependensi\"\u003eInstall dependensi\u003c/h3\u003e\n\u003cp\u003eSebelum menginstall Hugo pada openSUSE Tumbeleweed yang harus dilakukan adalah menginstall beberapa dependensi diantaranya adalah:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGo\u003c/li\u003e\n\u003cli\u003eGit\u003c/li\u003e\n\u003cli\u003eHugo\u003cbr/\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esudo\u003c/span\u003e \u003cspan class=\"n\"\u003ezypper\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ego\u003c/span\u003e \u003cspan class=\"n\"\u003egit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ewget\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"n\"\u003ehttps\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"o\"\u003e//\u003c/span\u003e\u003cspan class=\"n\"\u003egithub\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecom\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003egohugoio\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehugo\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ereleases\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003edownload\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ev0\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"mf\"\u003e71.0\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehugo_0\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"mf\"\u003e71.0\u003c/span\u003e\u003cspan class=\"n\"\u003e_Linux\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e64\u003c/span\u003e\u003cspan class=\"n\"\u003ebit\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etar\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egz\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etar\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003exvf\u003c/span\u003e \u003cspan class=\"n\"\u003ehugo_0\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"mf\"\u003e71.0\u003c/span\u003e\u003cspan class=\"n\"\u003e_Linux\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e64\u003c/span\u003e\u003cspan class=\"n\"\u003ebit\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etar\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egz\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esudo\u003c/span\u003e \u003cspan class=\"n\"\u003emv\u003c/span\u003e \u003cspan class=\"n\"\u003ehugo\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003eusr\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003elocal\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ebin\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch5 id=\"cek-versi-hugo\"\u003eCek Versi Hugo\u003c/h5\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehugo version\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eJika sudah berhasil terinstall akan menampilkan output seperti berikut :\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHugo Static Site Generator v0.71.0-06150C87 linux/amd64 BuildDate: 2020-05-18T16:08:02Z\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSelesai ~\u003c/p\u003e","title":"Install Hugo di openSUSE Tumbleweed"},{"content":"Install Dependencies Before install Google Cursor, please install dependencies\nsudo zypper in xcursorgen inkscape gnome-themes appmenu-gtk2-module appmenu-gtk3-module Build Google Cursor git clone https://github.com/KaizIqbal/Google_Cursor.git cd Google_Cursor chmod +x build.sh ./build.sh chmod +x ./Installer_Google.sh Install as Root User sudo ./Installer_Google.sh Install as Local User ./Installer_Google.sh Enjoy!\nSource : Google Cursor\n","permalink":"https://rifkiaz.my.id/posts/install-google-cursors-on-opensuse-tumbleweed/","summary":"\u003ch3 id=\"install-dependencies\"\u003eInstall Dependencies\u003c/h3\u003e\n\u003cp\u003eBefore install Google Cursor, please install dependencies\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper in xcursorgen inkscape gnome-themes appmenu-gtk2-module appmenu-gtk3-module\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"build-google-cursor\"\u003eBuild Google Cursor\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit clone https://github.com/KaizIqbal/Google_Cursor.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecd Google_Cursor\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod +x build.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e./build.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod +x ./Installer_Google.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"install-as-root-user\"\u003eInstall as Root User\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo ./Installer_Google.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"install-as-local-user\"\u003eInstall as Local User\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e./Installer_Google.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEnjoy!\u003c/p\u003e\n\u003cp\u003eSource : \u003ca href=\"https://github.com/KaizIqbal/Google_Cursor\"\u003eGoogle Cursor\u003c/a\u003e\u003c/p\u003e","title":"How To Install Google Cursor on openSUSE Tumbelweed"},{"content":"Ini beberapa proyek infrastruktur dan otomasi yang pernah saya kerjakan. Detail yang spesifik ke kantor sengaja nggak saya sertakan. Yang saya tulis adalah pendekatan dan pelajarannya.\nOtomasi MongoDB Replica Set dengan Ansible Role Ansible untuk memasang MongoDB replica set tiga node dengan keyfile, autentikasi aktif, dan inisialisasi yang idempoten. Rahasia dikelola dengan Ansible Vault.\nStack: Ansible, MongoDB, Ansible Vault, Ubuntu · Baca tulisannya →\nDeploy Prometheus Exporter dengan Ansible Role Ansible untuk memasang exporter Prometheus sebagai service systemd yang di-hardening, dengan verifikasi checksum dan daftar target scrape yang dibuat otomatis dari inventory.\nStack: Ansible, Prometheus, node_exporter, systemd · Baca tulisannya →\nAutomation Tools Internal Merapikan kumpulan script operasional menjadi satu CLI internal dengan subcommand, mode dry-run, output JSON, dan exit code yang konsisten supaya aman dipakai tim dan mudah dirangkai di pipeline.\nStack: Python, CLI, CI · Baca tulisannya →\nLayanan Paste Internal Pastebin internal untuk tim teknis sebagai pengganti pastebin publik: enkripsi di sisi klien, expiry, burn after reading, di belakang reverse proxy dengan autentikasi.\nStack: Docker, Nginx, PrivateBin · Baca tulisannya →\nMau ngobrol soal proyek serupa? Sapa saja saya lewat GitHub atau X/Twitter.\n","permalink":"https://rifkiaz.my.id/proyek/","summary":"\u003cp\u003eIni beberapa proyek infrastruktur dan otomasi yang pernah saya kerjakan. Detail yang spesifik ke kantor sengaja nggak saya sertakan. Yang saya tulis adalah pendekatan dan pelajarannya.\u003c/p\u003e\n\u003ch2 id=\"otomasi-mongodb-replica-set-dengan-ansible\"\u003eOtomasi MongoDB Replica Set dengan Ansible\u003c/h2\u003e\n\u003cp\u003eRole Ansible untuk memasang MongoDB replica set tiga node dengan keyfile, autentikasi aktif, dan inisialisasi yang idempoten. Rahasia dikelola dengan Ansible Vault.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStack:\u003c/strong\u003e Ansible, MongoDB, Ansible Vault, Ubuntu · \u003ca href=\"/posts/ansible-mongodb-replica-set/\"\u003eBaca tulisannya →\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"deploy-prometheus-exporter-dengan-ansible\"\u003eDeploy Prometheus Exporter dengan Ansible\u003c/h2\u003e\n\u003cp\u003eRole Ansible untuk memasang exporter Prometheus sebagai service systemd yang di-hardening, dengan verifikasi checksum dan daftar target scrape yang dibuat otomatis dari inventory.\u003c/p\u003e","title":"Proyek"},{"content":"\nHalo, saya Rifki Affandi. Sehari-hari saya berkutat di dunia infrastruktur dan DevOps.\nKerjaan saya kurang lebih seputar mengurus server Linux, mengotomasi provisioning pakai Ansible, menjaga database tetap jalan, dan memasang monitoring supaya masalah ketahuan sebelum pengguna sempat merasakannya. Saya juga suka bikin tooling internal kecil yang bikin kerjaan tim lebih ringan.\nYang biasa saya kerjakan Otomasi infrastruktur: Ansible (role, inventory, Vault), shell scripting, Python. Database: MongoDB replica set, MariaDB/MySQL. Observability: Prometheus, exporter, Grafana, alerting. Platform: Linux (openSUSE, Ubuntu, Debian), Docker, HAProxy, OPNsense, PowerDNS. CI/CD dan Git: GitHub Actions, alur kerja berbasis pull request. Komunitas open source Di luar kerjaan, saya aktif di komunitas FLOSS, termasuk openSUSE dan LibreOffice Indonesia, dan sempat jadi pembicara di GNOME.Asia Summit 2022 di Kuala Lumpur, membawakan topik seputar mechanical keyboard. Banyak tulisan di blog ini lahir dari hasil ngoprek bareng komunitas.\nSaya juga terdaftar sebagai openSUSE Member. Profil kontribusi saya bisa kamu lihat di openSUSE Kudos. Contekan perintah openSUSE yang sering saya pakai saya kumpulkan di halaman Cheat Sheet.\nTentang blog ini Blog ini isinya catatan teknis yang saya tulis supaya bisa dibaca ulang, buat saya sendiri dan buat siapa saja yang lagi ketemu masalah serupa. Contoh konfigurasinya sengaja dibuat generik, jadi sesuaikan dulu dengan lingkunganmu sebelum dipakai di produksi.\nKontak GitHub: github.com/rifkiaz openSUSE: kudos.opensuse.org/user/Rifkiaz X/Twitter: @rifkiiaz Instagram: @rifkiaz Konten blog ini berlisensi CC BY-NC-SA 4.0.\n","permalink":"https://rifkiaz.my.id/tentang/","summary":"\u003cp\u003e\u003cimg alt=\"Rifki Affandi menjadi pembicara di GNOME.Asia Summit 2022\" loading=\"lazy\" src=\"/img/rifki-gnome-asia.webp\" title=\"Saat jadi pembicara di GNOME.Asia Summit 2022\"\u003e\u003c/p\u003e\n\u003cp\u003eHalo, saya \u003cstrong\u003eRifki Affandi\u003c/strong\u003e. Sehari-hari saya berkutat di dunia infrastruktur dan DevOps.\u003c/p\u003e\n\u003cp\u003eKerjaan saya kurang lebih seputar mengurus server Linux, mengotomasi provisioning pakai Ansible, menjaga database tetap jalan, dan memasang monitoring supaya masalah ketahuan sebelum pengguna sempat merasakannya. Saya juga suka bikin tooling internal kecil yang bikin kerjaan tim lebih ringan.\u003c/p\u003e\n\u003ch2 id=\"yang-biasa-saya-kerjakan\"\u003eYang biasa saya kerjakan\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eOtomasi infrastruktur\u003c/strong\u003e: Ansible (role, inventory, Vault), shell scripting, Python.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDatabase\u003c/strong\u003e: MongoDB replica set, MariaDB/MySQL.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eObservability\u003c/strong\u003e: Prometheus, exporter, Grafana, alerting.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePlatform\u003c/strong\u003e: Linux (openSUSE, Ubuntu, Debian), Docker, HAProxy, OPNsense, PowerDNS.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCI/CD dan Git\u003c/strong\u003e: GitHub Actions, alur kerja berbasis pull request.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"komunitas-open-source\"\u003eKomunitas open source\u003c/h2\u003e\n\u003cp\u003eDi luar kerjaan, saya aktif di komunitas FLOSS, termasuk openSUSE dan LibreOffice Indonesia, dan sempat jadi pembicara di GNOME.Asia Summit 2022 di Kuala Lumpur, membawakan topik seputar mechanical keyboard. Banyak tulisan di blog ini lahir dari hasil ngoprek bareng komunitas.\u003c/p\u003e","title":"Tentang Saya"}]