--- --- layout: post title: "PaX basic" description: "The very fundamental of PaX" date: "2024-11-01T00:00:00Z" thumbnail: "/img/kernel-hardening.jpg" --- I'm tired of chasing individual CVEs. What if we could eliminate that entire class of vulnerabilities altogether?🤔 This is the first post of this series where I look into solution to solve the above problem. PaX is one of the attempt to do so. If it's so good, why hasn't it make it into kernel mainline? etc... # PaX Overview **PaX** is a security patchset developed by gcsecurity that focuses on memory corruption protection. It implements various features designed to defend against code injection and execution attacks, such as buffer overflows and ROP (Return-Oriented Programming) exploits. Below are the key features of PaX. --- ## Key Features of PaX ### 1. **PAGEEXEC (Paging-based Non-Executable Pages)** PAGEEXEC is a sophisticated mechanism that implements non-executable memory protection to enhance security. In Unix-like operating systems, memory pages can be set to read, write, or execute. If a page is both writable and executable, attackers can potentially inject malicious code into it and execute it—this is a common exploitation tactic. **How PAGEEXEC Works:** - **NX Bit Enforcement:** PAGEEXEC uses the NX (non-executable) bit in modern processors to prevent code execution in memory pages marked as data-only. If code is executed in these areas, PAGEEXEC triggers an exception, blocking the exploit. - **Software Simulation on Older CPUs:** On CPUs that don't support the NX bit, PAGEEXEC emulates this protection using software-based techniques (e.g., segment-based permissions on x86 architectures). **Targeted Attacks:** - PAGEEXEC helps defend against attacks like buffer overflows, stack overflows, and certain types of ROP attacks by ensuring writable pages cannot be executed. **Compatibility Considerations:** - Programs like **Google Chrome NaCl Helper** (which uses dynamic code generation via JIT compilers) may require disabling PAGEEXEC for compatibility. Most of the computers sold in the last 20 years already has NX supported. --- ### 2. **SEGMEXEC (Segmentation-based Non-Executable Pages)** > A feature that prevents executing code in the data segment using segmentation on x86_32. SEGMEXEC is implemented through the segmentation feature of x86_32 architecture, making it applicable only to x86_32 systems. It functions similarly to PAGEEXEC but is specific to the segmentation method used in this architecture. --- ### 3. **MPROTECT (W^X Writable-Xor-Executable Memory Pages)** MPROTECT restricts memory pages from being both writable and executable, which is a common protection mechanism against buffer overflow and code injection attacks. **How MPROTECT Works:** - PAGEEXEC prevents code execution in data regions, while MPROTECT ensures that applications cannot make memory pages both writable and executable. - MPROTECT works in conjunction with PAGEEXEC to enforce stricter memory protection, especially for attacks that attempt to modify memory permissions during runtime, such as JIT compilation. **Targeted Attacks:** - Code injection - JIT attacks - Certain ROP exploits **Examples of Affected Programs:** - QEMU - Chromium - Python interpreter - Java VM | Feature | PAGEEXEC | MPROTECT | |-------------------|------------------------------------------------|-------------------------------------------------| | **Focus** | Enforces non-executable memory for non-code pages | Restricts making pages both writable and executable | | **Protection Mechanism** | Uses NX bit or segmentation tricks | Blocks runtime memory permission changes by applications | | **Targeted Attacks** | Buffer overflows, code injection | Code injection, JIT attacks, certain ROP exploits | | **Behavior** | Automatic enforcement of non-executable memory | Requires explicit permission for writable-executable memory | --- ### 4. **EMUTRAMP (Emulated Trampolines)** **Emulated trampolines** are short pieces of executable code that redirect execution flow, often used in JIT compilation or during ROP chain exploits. These trampolines allow attackers to jump between different code sections, enabling payload execution or control flow redirection. **How EMUTRAMP Works:** - EMUTRAMP blocks exploits that attempt to hijack execution through trampolines, ensuring that they cannot be created, modified, or executed in ways that compromise system integrity. --- ### 5. **ASLR (Address Space Layout Randomization)** ASLR is a security technique that randomizes memory addresses used by system components, such as the stack, heap, libraries, and memory mappings. This makes it more difficult for attackers to predict memory locations and successfully exploit vulnerabilities. **PaX vs. Linux Kernel ASLR:** | Feature | **PaX ASLR** | **Linux Kernel ASLR** | |------------------------------|---------------------------------------------------|---------------------------------------------------| | **Randomization Range** | Larger, more aggressive randomization of memory | Limited randomization, smaller range than PaX | | **Randomization Granularity** | More fine-grained randomization (stack, heap, libraries, etc.) | Randomizes stack, heap, and shared libraries | | **Security Level** | Stronger protection against advanced attacks like ROP | Basic protection against simple buffer overflows | | **Flexibility** | Less flexible, requires PaX patches for the kernel | Configurable via sysctl parameters | | **Performance Impact** | May have a higher performance overhead due to aggressive randomization | Generally lower overhead in comparison | --- ### PaX Flags This is the list of PaX flags you can mask binaries with. ``` -p|P PAGEEXEC: use NX-bit to mark unexecutable pages -e|E EMUTRAMP: emulate stack trampolines -m|M MPROTECT: write-xor-execute in mmap/mprotect(2) syscalls -r|R RANDMMAP: address space layout randomization (ASLR) -s|S SEGMEXEC: segmentation-based NX-bit emulation ``` --- In the second post of this series, I will focus more on why this hasn't made it into kernel mainline yet. --- --- layout: post title: "Happy computing" description: "Happy computing" date: "2024-11-01T00:00:00Z" thumbnail: "/img/twin-engines.jpg" draft: true --- I have a pretty significant IT footprint at home, to support my FOSS maintenance & development needed to facilitate everything I do. ## Overview of my setup I have a cluster of multiple servers, ranging from AMD Threadripper PRO machine to tiny ARM machines & multiple NUC boxes. They each serve their own purpose, in various form from virtual machine to container in Kubernetes cluster. Most VM has their own local storage but for workload that require persistent volumes, I prefer to use my Synology NAS. ## My main workstation My primary development machine is named [twin-engines](https://tuananh.net/2024/02/28/machine-learning-rig/). It's a pretty hefty amd64 box, equipped with AMD Threadripper PRO 3995X (64 cores/ 128 threads) and 256GB of ECC RAM. I used to run Pop!_OS on it but quickly moved back to Arch Linux. Life without a proper tiling windows manager is just unbearable. And most of CUDA stuff works just fine on Arch. I was a bit worrying at first but things turn out just fine. I use KVM and libvirt to manage various servives I have on this server. ### OS I used to use Ubuntu previously but moved to ArchLinux for the last few years. I like rolling release distro. Part of my work is container-related so having a more recent kernel helps with that. ### Tiling window manager I've used i3, sway and they both work wonder for me. According to my need, I guess any tiling wm would suffice. - customizable shortcuts (of-course) - border highlight for active window - customizable status bar. ## Kubernetes Most of the services I hosted are in either virtual machines or containers. Using Kubernetes is an obvious choice because of the natural of my work. Having an universal controlplane is nice. --- --- layout: post title: "On adopting Chainguard Images" description: "On adopting Chainguard Images" date: "2024-06-28T00:00:00Z" thumbnail: "/img/chainguard.jpg" --- In this post, I'll discuss my experience transitioning to Chainguard Images and the rationale behind this decision. ## The challanges The conventional vulnerability management process often proves cumbersome and inefficient. Here's a typical scenario from my past experience: - **Periodic scans**: every xx months (usually once a quarter), security team will scan organization-wide and send vulnerability reports to all service owners. - **Manual update process**: Service owners become bogged down with updating operating systems and application libraries. - **On-premise constraint**: On-premises environments add complexity due to restricted permissions (e. g., backups, testing, rollback). - **Limited scalability**: Traditional methods impede scalability. For instance, an organization I worked for once estimated an entire year for OS patching. It's a true story. Back in 2021, I aimed to solve this problem once and for all. So basically, we have 2 kinds of workload: one on virtual machines (VM) and the other one is container workload (EKS, RedHat OCP, etc...). I decided to target containerized workloads for initial improvement due to their inherent manageability. ## Evaluating Container Image Solutions ### Distroless I like [GoogleContainerTools/distroless](https://github.com/GoogleContainerTools/distroless). It's really good when it works for you but customization presents a significant hurdle [^1]. The beta-status [rules_distroless](https://github.com/GoogleContainerTools/rules_distroless) might offer some future relief, but the underlying reliance on Bazel remains a barrier. Another issue is that distroless is based on Debian. And while Debian offers a stable and well-tested foundation, its release cycle might not align perfectly with the need for rapid vulnerability patching. Debian adheres to a scheduled release cadence, which may not prioritize fixing specific CVEs (Common Vulnerabilities and Exposures) as quickly as we require. For scenarios demanding more immediate vulnerability mitigation, a rolling release operating system might be a more suitable choice. ### Alpine-based Another popular option is using Alpine OS. But the problem is software support & compatibility due to its use of musl libc instead of glibc. For applications offering Alpine-based images (e.g., Redis), utilizing them is ideal. However, building applications on top of Alpine can be troublesome - **Language-specific challenges**: Languages providing prebuilt binaries (e.g., Python with PyPI, Node.js with NPM) encounter issues when maintainers haven't provided Alpine packages. This necessitates building from source, a time-consuming process requiring numerous build tools within the build image. - **Limited influence**: Convincing maintainers to support Alpine by offering prebuilt packages might be feasible, but often developers must resort to building them themselves. ### Chainguard Images Traditional container image solutions presented limitations, prompting me to explore Chainguard Images. Their key benefits include: - **Reduced attack surface**: Chainguard Images minimize the attack surface by incorporating only essential components. This significantly lowers the number of potential vulnerabilities compared to industry alternatives. - **Streamlined patching**: Daily patching ensures vulnerabilities are addressed promptly, eliminating the need to wait for upstream distributions. - **Enhanced security**: Cryptographically signed images with SBOMs (Software Bill of Materials) and verifiable provenance bolster security posture. ## A powerful blend When you see this closely, Chainguard Images combines the strengths of Distroless and Alpine with additional benefits. - **Philosophy and expertise**: Founded by Dan Lorenc and Matt Moore (creators of distroless), Chainguard Images inherits the distroless philosophy of minimizing attack surface. It represents their next iteration, prioritizing an improved developer experience. - **Better compatibility**: Unlike Alpine's musl libc, Chainguard Images leverage glibc (as in Distroless), ensuring broader software compatibility. - **Familiar tooling**: Developers accustomed to Alpine's tooling ecosystem (e.g., apk) will find a similar experience with Chainguard Images. This is likely due to Ariadne Conill, a core Alpine Linux maintainer, is the one bootstrapping Chainguard's [Wolfi OS](https://github.com/wolfi-dev/os) and its key components: [melange](https://github.com/chainguard-dev/melange) & [apko](https://github.com/chainguard-dev/apko). - **Simplified customization**: Chainguard Images offer a significant advantage in customization. Unlike Bazel used by Distroless, Chainguard employs a more approachable YAML format for configuration, streamlining the process for developers & contributors. ## The people One of few things I love about Chainguard is its people. They are packed with talents. - There were Ariadne Conill - a highly regarded, long time core contributor to Alpine Linux. - There were Dan Lorenc & Matt Moore, the original creators of distroless. - There were Ville Aikas - co-creator of Kubernetes. - There were Jason Hall who cofounded GCP CloudBuild & Tekton, among lots of other things. - And so many more like James Rawlings, Nghia Tran, Adrian Mouat, Josh Dolitsky, Carlos Panato, Dan Luhring, etc..... And this give me confidence that they are poised for success. In corporate environments, adopting new technologies often prioritizes long-term viability and a proven track record of success. This ensures a stable foundation for critical applications and minimizes the risk of disruption from short-lived solutions. And I want to be a small part of its success by being an [early contributor](https://github.com/wolfi-dev/os/commits?author=tuananh) to Wolfi OS and advocate for them here in Vietnam (I gave [a talk about Wolfi OS](https://tuananh.net/talks/) at FOSSASIA Summit earlier this year & another one at [DevSecOps Leadership Forum Singapore](https://www.sonatype.com/devsecops-leadership-forum-singapore-2024) 2024) ## Last words If you're looking for a secure and efficient way to manage containerized workloads, Chainguard Images are definitely worth exploring. Their commitment to security and their impressive team inspire confidence in their ability to provide a long-term solution for the evolving container landscape. [^1]: https://github.com/GoogleContainerTools/distroless/issues/1321 --- --- layout: post title: "Machine learning rig" description: "My notes while building a machine learning rig" date: "2024-02-28T00:00:00Z" thumbnail: "/img/twin-engines.jpg" --- ## Building a machine learning rig This is my notes while building a machine learning rig. After a bunch of research, I ended up with the following specs: - CPU: [Threadripper PRO 3995WX](https://www.amd.com/en/products/processors/workstations/ryzen-threadripper.html). - Mainboard: [Supermicro M12SWA-TF](https://www.supermicro.com/en/products/motherboard/m12swa-tf). - Cooler: [Enermax TR4 500W](https://www.enermax.com/en/products/liqtech-tr4-ii-series-360mm-cpu-liquid-cooler) - GPU: 2x [NVIDIA 3090 24GB Founder Edition](https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3090-3090ti/). - RAM: 256GB (8x 32GB stick) memory ECC DDR4 3200Mhz. - Storage: [2TB SSD Samsung 970 EVO Plus](https://www.samsung.com/vn/memory-storage/nvme-ssd/970-evo-plus-nvme-m-2-ssd-2tb-mz-v7s2t0bw/). - PSU: [Super Flower 1200W 80 Plus](https://memoryzone.com.vn/nguon-may-tinh-superflower-leadex-platinum-1200w-se-80-plus-platinum-sf-1200f14mp). - Case: [LIAN-LI Lancool 216 Mesh Black](https://www.amazon.com/Lian-Li-Tempered-Computer-Included/dp/B0BN3SY5XW). - OS: [Archlinux](https://archlinux.org/) ### Picture ![](/img/twin-engines.jpg) ### CPU You need one with multiple PCIe lanes, which usually found in server CPUs since they have lots of memory. CPU specs doesn't matter much for ML rig. I could have gone with Threadripper 1st gen and it would probably be fine. ### GPU What you want is lots of VRAM. I figure with 2x 3090, I would be able to run 70B model with 4-bit quantization. Maybe I can add another card later and would be enough for 70B model + 8bit quantization. General rule of thumb: > Take the parameter count in billions and multiply it by 2. This will tell you roughly how many gigs the full sized model requires. > > The parameter count in billions is roughly equal to the 8-bit quants. > > The parameter count in billions divided by two is roughly equal to the 4-bit quants. So 70B model will have the following sizes: > Full = 140 gig > > 8-bit = 70gig > > 4-bit = 35 gig Source: [Reddit](https://www.reddit.com/r/LocalLLaMA/comments/19ayhg7/building_a_machine_for_selfhosted_llama_will_2_x/) ### PSU I went with 1200W PSU. While others say that it's fine with 2x 3090, I also went ahead and undervolt the 3090 to 280w limit. Make sure you have persistence mode turned on. Minor, ignorable performance drop but greatly decrease thermal & power consumption. - First, update `nvidia-persistenced.service` to `persistence-mode`. - Create a new systemd service to set power limit on startup. Make sure it start after `nvidia-persistenced.service`. ![undervolt](/img/undervolt.png) - Verify it's working after boot. It should looks like this with `nvidia-smi` command. Notice the Persistence-M is On and power limit is 280W. ```sh +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 545.29.06 Driver Version: 545.29.06 CUDA Version: 12.3 | |-----------------------------------------+----------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA GeForce RTX 3090 On | 00000000:01:00.0 On | N/A | | 0% 34C P8 22W / 280W | 664MiB / 24576MiB | 2% Default | | | | N/A | +-----------------------------------------+----------------------+----------------------+ | 1 NVIDIA GeForce RTX 3090 On | 00000000:21:00.0 Off | N/A | | 0% 26C P8 16W / 280W | 12MiB / 24576MiB | 0% Default | | | | N/A | +-----------------------------------------+----------------------+----------------------+ +---------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=======================================================================================| | 0 N/A N/A 3485 G /usr/lib/xorg/Xorg 225MiB | | 0 N/A N/A 3619 G /usr/bin/gnome-shell 216MiB | | 0 N/A N/A 5169 G firefox 207MiB | | 1 N/A N/A 3485 G /usr/lib/xorg/Xorg 4MiB | +---------------------------------------------------------------------------------------+ ``` ### Case Any ATX case would do. I went with LIAN-LI Lancool 216. Most ATX desktop case would be able to house 2x 3090 cards. If you want to use 3 cards, you will have to use a smaller card, or bigger case. ## OS I decided to go with [Pop!_OS](https://pop.system76.com/). It's pretty much Ubuntu but better. Pop!_OS offers a NVIDIA variant with some NVIDIA-related stuff baked in. While I love Arch with Sway, it's not an option. NVIDIA does not play nice with Wayland. So Pop!_OS and X.org it is. ## Installation ### NVIDIA driver Pop!_OS NVIDIA variant comes up NVIDIA driver v545 by default (as of this post). It seems to work well for me. However, it doesn't, maybe try downgrading to an older version. Some reported that [v530](https://samsja.github.io/blogs/rig/part_2/#debugging) or [v535](https://forums.developer.nvidia.com/t/rtx-3090-nvlink-cuda-p2p-not-working-on-linux-or-windows-in-different-ways/232673/9) works better for them. ### NVIDIA Container Toolkit Don't use the guide found on Pop!_OS's website. Instead, follow the official guide from NVIDIA website [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). ### Simple benchmark with hashcat ```sh sudo apt update && sudo apt install hashcat # hashcat -b -m # -b: benchmark mode # -m 0: md5 hash type hashcat -b -m 0 ``` Expected output ```sh CUDA API (CUDA 12.3) ==================== * Device #1: NVIDIA GeForce RTX 3090, 23307/24258 MB, 82MCU * Device #2: NVIDIA GeForce RTX 3090, 23987/24259 MB, 82MCU OpenCL API (OpenCL 3.0 CUDA 12.3.99) - Platform #1 [NVIDIA Corporation] ======================================================================= * Device #3: NVIDIA GeForce RTX 3090, skipped * Device #4: NVIDIA GeForce RTX 3090, skipped Benchmark relevant options: =========================== * --optimized-kernel-enable ------------------- * Hash-Mode 0 (MD5) ------------------- Speed.#1.........: 70679.3 MH/s (38.63ms) @ Accel:128 Loops:1024 Thr:256 Vec:8 Speed.#2.........: 70505.2 MH/s (38.63ms) @ Accel:128 Loops:1024 Thr:256 Vec:8 Speed.#*.........: 141.2 GH/s ``` ### Tensorflow Docker for everything :) This is the command I use to launch a new container with Tensorflow ```sh docker run -v $PWD:/workspace -w /workspace \ -p 8888:8888 \ --runtime=nvidia -it --rm --user root \ tensorflow/tensorflow:2.15.0-gpu-jupyter \ bash ``` Created a simple script to test it, named `hello-world.py` with following content and test. ```python #!/usr/bin/python3 import tensorflow as tf hello = tf.constant('Hello, TensorFlow!') tf.print(hello) tf.print('Using TensorFlow version: ' + tf.__version__) with tf.device('/gpu:0'): a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a') b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b') c = tf.matmul(a, b) tf.print(c) ``` You can also launch Jupyter notebook in there and access it from the host machine via `127.0.0.1:8888`. ```sh jupyter notebook --ip 0.0.0.0 --no-browser --allow-root ``` ### SysFS had negative value (-1) error Find GPU pci id with ```sh ls /sys/module/nvidia/drivers/pci:nvidia/ # 0000:01:00.0 0000:21:00.0 bind module new_id remove_id uevent unbind ``` And then ```sh echo 0 | tee /sys/module/nvidia/drivers/pci:nvidia/0000:01:00.0/numa_node echo 0 | tee /sys/module/nvidia/drivers/pci:nvidia/0000:21:00.0/numa_node ``` ref: [StackOverflow](https://stackoverflow.com/questions/44232898/memoryerror-in-tensorflow-and-successful-numa-node-read-from-sysfs-had-negativ/44233285#44233285) Or you can run [this script](https://gist.github.com/zrruziev/b93e1292bf2ee39284f834ec7397ee9f?permalink_comment_id=4587829#gistcomment-4587829) to fix it. ```sh #!/usr/bin/env bash if [[ "$EUID" -ne 0 ]]; then echo "Please run as root." exit 1 fi PCI_ID=$(lspci | grep "VGA compatible controller: NVIDIA Corporation" | cut -d' ' -f1) #PCI_ID="0000:$PCI_ID" for item in $PCI_ID do item="0000:$item" FILE=/sys/bus/pci/devices/$item/numa_node echo Checking $FILE for NUMA connection status... if [[ -f "$FILE" ]]; then CURRENT_VAL=$(cat $FILE) if [[ "$CURRENT_VAL" -eq -1 ]]; then echo Setting connection value from -1 to 0. echo 0 > $FILE else echo Current connection value of $CURRENT_VAL is not -1. fi else echo $FILE does not exist to update. fi done ``` ## Misc ## nvlink Apparently, you need this to better ultilize multiple GPU cards. The question remains whether you see it's worth it or not. Some says that the performance gain is not worth it. - [Did nvlink work for anyone with 2x 3090](https://www.reddit.com/r/LocalLLaMA/comments/16kl62b/did_nvlink_work_for_anyone_with_2x_3090s/) - [How to configure nvlink on Ubuntu](https://docs.digitalocean.com/products/paperspace/machines/how-to/configure-nvlink/) --- --- layout: post title: "Roblox on Linux" description: "Best way to play Roblox on LInux" date: "2024-02-01T00:00:00Z" thumbnail: "/img/roblox.png" --- I play game with my kid every now and then. His favorite game is Roblox. Unluckily, there's no official Roblox client on Linux. So that leaves me with the only option of running emulation of one of the supported platform. I've tried many ways and finally settled on Waydroid. ## Bliss OS = 👎 [Bliss OS](https://blissos.org/) with QEMU is the first option I tried. While it works and very simple to setup, the performance is abysmal, no matter how many CPU core I tried throwing at it. There's also a little problem with the mouse cursor's position if you change resolution once the QEMU emulator already started. ## Genymotion I installed Genymotion and everything. The installation experience is very good, really easy to use. However, Genymotion [doesn't offer aarch64 translation (only armv7 one)](https://support.genymotion.com/hc/en-us/articles/360010029677-How-to-run-applications-for-arm64-aarch64-armv8) and Roblox is aarch64 app. But if you're using one of the new Apple machine with M1, M2, ... chips, then you can explore this option. I haven't went that far because I do not own one. ## Cloud gaming Another option is go with cloud gaming. There are plenty options out there, something like [boosteroid](https://boosteroid.com/). CloudBase has a list of which cloud gaming do support Roblox [here](https://cloudbase.gg/g/roblox/). As of this post, only boosteroid does. Make sure your games are supported and the latency is low enough (Most of them do have latency test for you to check before you subscribe). ## Waydroid [Waydroid](https://waydro.id/) is a bit more complicated. It's Wayland only so if you use Xorg, it's out of the question. Actually, it's not but it's getting quite complicated. You will need to install waydroid like normal, run weston (a Wayland compositor) and then run `waydroid show-full-ui` command in there. It also requires a custom kernel ([linux-zen](https://archlinux.org/packages/extra/x86_64/linux-zen/) or you can compile yourself). I would recommend you to go with linux-zen if you're using Arch or an Arch-variant since you don't have to compile it yourself. Compiling kernel can take maybe 10-15 mins, depending on your computer's specs. The upside of using Waydroid is performance is much much better compared with BlissOS. Installation is mostly smooth. I just need to follow the instruction on [Waydroid page on Arch wiki](https://wiki.archlinux.org/title/Waydroid) but there are certain things that ain't documented there. ### Network doesn't work > The script /usr/lib/waydroid/data/scripts/waydroid-net.sh prefers nft, iptables-legacy, iptables, in that order. > There are 2 problems with that logic: > - nftables doesn't work in combination with iptables rules (which I have on due to Docker) > - iptables-legacy doesn't work at all ```sh sudo sed -i~ -E 's/=.\$\(command -v (nft|ip6?tables-legacy).*/=/g' \ /usr/lib/waydroid/data/scripts/waydroid-net.sh ``` The command above fixes it for me. Thanks [rustyx](https://github.com/waydroid/waydroid/issues/143#issuecomment-1520857943). ### Resolution To set height & width, use the following command. Change your prefered resolution accordingly. ```sh waydroid prop set persist.waydroid.width 2560 waydroid prop set persist.waydroid.height 1440 ``` And restart waydroid (`waydroid session stop`) ### Can't install games/apps By default, waydroid doesn't come with ARM translation. There's `libndk` or `libhoudini`. `libndk` is for AMD and `libhoudini` is for Intel. Or you can try both to see what works for you. For example, in my case, `libndk` doesn't work with Roblox but `libhoudini` does. There's this project call [waydroid_script](https://github.com/casualsnek/waydroid_script) that help you with that. ### This device isn't Play Protect certified Run `sudo waydroid shell` and then issue the following command ```sh ANDROID_RUNTIME_ROOT=/apex/com.android.runtime ANDROID_DATA=/data ANDROID_TZDATA_ROOT=/apex/com.android.tzdata ANDROID_I18N_ROOT=/apex/com.android.i18n sqlite3 /data/data/com.google.android.gsf/databases/gservices.db "select * from main where name = \"android_id\";" ``` Copy the number string and add it over at [https://www.google.com/android/uncertified](https://www.google.com/android/uncertified) This is also documented on [Waydroid's FAQ](https://docs.waydro.id/faq/google-play-certification) ## Clipboard sharing You need to have `python-pyclip` installed in order for clipboard sharing to work. I'm on Arch so what I need to do is ```sh yay -S python-pyclip ``` ## Touch ain't registered You may need to enable `fake_touch` for this to work. Details [here](https://docs.waydro.id/usage/waydroid-prop-options). The value should be a string, comma separated of all the apps id you want to enable this feature. If you want to do it for all apps, use `*`. ```sh waydroid prop set persist.waydroid.fake_touch "*" ``` --- --- layout: post title: "Reproducibility" description: "Reproducibility, why it matters and how to achieve reproducibility with container images" date: "2024-01-21T00:00:00Z" thumbnail: "/img/security.jpg" --- ## What is reproducibility? > Reproducible builds are a set of software development practices that create an independently-verifiable path from source to binary code. as defined by [reproducible-builds.org](https://reproducible-builds.org/) ## Why does it matter? > The motivation behind the Reproducible Builds project is therefore to allow verification that no vulnerabilities or backdoors have been introduced during this compilation process So assume reproducibility can be **achieved** and **attested**, and if we trust the published source, this artifact can be trusted too. There are 2 concepts here: - "**achieved**" means reproducible. - "**attested**" means verified reproducible. This is very crucial in the software supply chain security. ## How to produce a reproducible container image Let's take an `go:1.21-bookwarm` image for example. Source for Dockerfile is available [here](https://github.com/docker-library/golang/blob/d1ff31b86b23fe721dc65806cd2bd79a4c71b039/1.21/bookworm/Dockerfile). ```dockerfile FROM buildpack-deps:bookworm-scm # install cgo-related dependencies RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ g++ \ gcc \ libc6-dev \ make \ pkg-config \ ; \ rm -rf /var/lib/apt/lists/* ``` Let's try to make this image reproducible. By default, the official `debian` image use `ftp.debian.org`. Packages in this repository get updated and old packages get dumped all the time. So reproducibility is pretty much gone. Thanksfully, there's `http://snapshot.debian.org` which provides access to old packages based on dates & version numbers. You can also use `http://snapshot-cloudflare.debian.org/archive/` as alternative. So how do we get reproducible build with this? By locking down the snapshot of the repository as the build time. By default, the content of default repo at `/etc/apt/sources.list.d/debian.sources` looks like this ``` Types: deb # http://snapshot.debian.org/archive/debian/20240110T000000Z URIs: http://deb.debian.org/debian Suites: bookworm bookworm-updates Components: main Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg Types: deb # http://snapshot.debian.org/archive/debian-security/20240110T000000Z URIs: http://deb.debian.org/debian-security Suites: bookworm-security Components: main Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg ``` Notice the commented line `http://snapshot.debian.org/archive/debian-security/20240110T000000Z`. This is the snapshot URL. If we use this URL, we can lock down the version of all the packages we want to install. But how do we get that URL? It's simple. ```sh SOURCE_DATE_EPOCH=$(stat --format=%Y /etc/apt/sources.list.d/debian.sources) # => 1704844800 printf "%(%Y%m%dT%H%M%SZ)T\n" "$SOURCE_DATE_EPOCH" # => 20240110T000000Z # => http://snapshot.debian.org/archive/debian-security/20240110T000000Z ``` Now if you run `apt update`, you will get the sth like this instead ``` apt update Get:1 http://snapshot-cloudflare.debian.org/archive/debian/20240110T000000Z bookworm InRelease [151 kB] Get:2 http://snapshot-cloudflare.debian.org/archive/debian-security/20240110T000000Z bookworm-security InRelease [48.0 kB] ``` And now, you can reliably reproduce this image because the rest of the go's Dockerfile is already reproducible (downloading go with shasum check). --- --- layout: post title: "Cloud cost optimization at scale part 1" description: "Cloud cost optimization at scale part 1: how we save approx 150k USD a year by simply shutting things off" date: "2024-01-21T00:00:00Z" thumbnail: "/img/cloud-cost-optimization.jpg" --- [👉 Discussion on HackerNews](https://news.ycombinator.com/item?id=39091416) ## The best optimization is simply shutting things off You work from 8am to 8pm. That's 12 hours. What happened to the rest of the day? You go home. So you shut things down. That's 50% cost saving right there. Simple enough right? Now add the following constraints: - Imagine if you have an AWS organization with hundreds of workload accounts. I've seen org with over 700 accounts. - Imagine if you want to support resources other than EC2. - Now add Kubernetes to the mix. Now your EC2 VM is being controlled by autoscaling group/cluster autoscaler or karpenter. You can't simply shut those off. They will be spin up right back. - Your team is small. You cant have them support all the teams within organization. You need a solution that can be dynamic (to support multiple resources, different requirements from different teams,etc..) but also easy enough for other team to operate it themselves. ## Cloud Automation Platform I was thinking a bigger picture. I want a simple way to automate the whole cloud platform: - Automate shutting down resources during off-work hours. - Discover certain event, correcting things - Etc... I like Lambda, Step Functions and EventBridge. Let's build something out of those. Here's what I had in mind initially ![](/img/cloud-automation.png) We changed the design a little bit by instead of assuming role, we just publish an event to the spoke account eventbus. We didn't want the automation executor to have abilties to assume role everywhere in the org. With this system in placed, we can do lots of stuff like - Whenever an EKS cluster is created within our org, we want to inject some security baseline policies in. - Whenever a Cloudwatch logs group created, we check for certain tag and if one presented, we ship it to a centralized account. - Or we can do stuff like auto-shutdown resources like we want initially at the beginning of the post. ## The deployment Now for this to work, we need to have: - (1) a consistent way of deploying stuff into spoke accounts. - (2) We also need a way to protect those resources (this one is actually easy, we can use tag.) As for (1), we are currently using [AWS AFT](https://docs.aws.amazon.com/controltower/latest/userguide/aft-overview.html). They have a mechanism for us to define baseline. Baseline is whatever resources you want to deploy to every accounts within an Organization Unit. Sounds exactly like what we need right. This kind of "baseline" is kinda like our agents, embeded in every spoke account. The rest of this is just - (1) agent discover stuff, send event to controlplane. - (2) controlplane send command back to spoke account to act accordingly. We built all these with Lambda, Step Functions and EventBridge. We love EventBridge pattern matching so much we created a small, [tiny Python module to be able to test pattern matching locally](https://github.com/tuananh/py-event-ruler). ## Back to the cloud cost optimization problem Now that we have a generic mechanism to automate stuff in our org. Let's do some $$ saving. For this specific case, we don't need our embedded agent to send us anything. We can simply send the "shutdown" command from controlplane. We have a little database for config customization if needed. This is a small Git repo where owners/operators of those spoke accounts can customize their schedule according to their need. What they can config is a pattern (EventBridge pattern style) along with what kind resouces to apply and the action accordingly. We also populate some more properties into the pattern payload like - `is_holiday`: bool. This one is pre-evaluated with our national public holiday. Users can then use this in their pattern matching. Eg. if holiday, do not turn them on at 8am. - `day_of_week`: Mon, Tue, etc... Users can then use this field to add a rule to turn things on at 8am from Mon to Fri. - and more ... Once the agent receives the payload, they will act accordingly. For examples, EC2 will be send to a Lambda function that handle EC2 and RDS will be sent to another Lambda function that handle RDS. It's quite easy to add another resouce to the supported list by simply adding more Lambda function handler. We make this feature an opt-out feature on our non-production OU (Organization Unit). If they want to turn this off for certain resources, they will have to tag those resources accordingly. ## The result Our compute (EC2, RDS, etc...) non-production costs reduce by over 70% during off-work hours. We projected to save approx ~150k USD during 1 year (assume that our cost will not grow). It's a small piece of our cost but we are still having many other optimizations wip. There are certain workload that are a bit more complicated like Kubernetes I mentioned earlier. Those will be solved next in part 2 of this series. Stay tuned! --- --- layout: post title: "Writing your first Wolfi package" description: "A complete tutorial on how to write your first Wolfi package" date: "2024-01-13T00:00:00Z" thumbnail: "/img/wolfi-pkg.jpg" --- The [Write your first Wolfi package](https://github.com/wolfi-dev/os/blob/main/CONTRIBUTING.md#write-your-first-wolfi-package) contributing guideline on Wolfi repo is a bit vague for beginner so I thought a more detailed, hands-on tutorial would benefit first-time contributor. ## Local dev environment The first step of contributing is to setup a build environment locally. Thanksfully, Wolfi team makes it very easy by just running `make dev-container` from the root of the repo. This assumes that you already have Docker installed. From here on, let's assume you're already inside the dev-container. ## How to build an existing package Each of the package in Wolfi repo consists of 2 things. Let's say we have a package name `crane`. I pick `crane` because it's a very typical Go project and relatively easy to build. - a YAML file `crane.yaml` - and an OPTIONAL folder of the same name `crane` if you have any patches. Any files you have in this folder will be copied in the workspace of the build environment. To build the package, you just have to run `make package/` and in this case, it will be `make package/crane`. That's how you can build a package locally. In the next part, I will go into detail of how you can package one. ## Writing your first Wolfi package Let's take a look at `crane.yaml` ### Basic package information ```yaml package: name: crane version: 0.17.0 epoch: 3 description: Tool for interacting with remote images and registries. copyright: - license: Apache-2.0 dependencies: runtime: - ca-certificates-bundle ``` The first section is the basic information about the package: name, version , epoch, description, etc.. Those are pretty self-explainatory. The only thing you need to note here is `epoch` field. You need to increase `epoch` in case you are re-building the same version of the software like: - bumping deps to fix CVEs. - fix a packaging issue. - etc... When you bump version of the package itself, make sure you reset the `epoch` back to `0`. The `dependencies` block describes what kind of package you need as runtime dependencies for your package. For example, `crane` says here that it needs `ca-certificates-bundle` as runtime dependencies. ### Build environment Let's move on to the next block `environment`. ```yaml environment: contents: packages: - busybox - ca-certificates-bundle - go environment: CGO_ENABLED: "0" ``` This one here let you describe what's needed to setup build environment. In our case, it's a go package so you need `go` obviously, `busybox` for some basic commands like `ln`, `mv` and stuff like that and `ca-certificates-bundle` since you need to download go deps. The `environment.environment` block lets you define any environment variables if you have any. ### Build pipeline This here is obivously the most important part of the package manifest. If you're familiar with GitHub Actions, you may find it very similar. `pipeline` is an array of build step. Each one of them is either a `runs` block that looks like below. It let you define any abitrary commands you want to run. ```yaml pipeline: - runs: | export CFLAGS="$CFLAGS -D_GNU_SOURCE" ./configure \ --build="$CBUILD" \ --host="$CHOST" \ --prefix=/usr \ --mandir=/usr/share/man \ --sbindir=/sbin \ --sysconfdir=/etc \ --without-kernel \ --enable-devel \ --enable-libipq \ --enable-shared ``` Or it can be an `uses` block which let you use a built-in step of `melange`. The list of the all the built-in steps can be found in `melange` repo [here](https://github.com/chainguard-dev/melange/tree/main/pkg/build/pipelines). Some of the most common one you will use are - `fetch`: which let you download from an URL - `git-checkout`: which let you clone from a git repo. You can use this one in case you want to add Git commit hash to your build command for example. - `autoconf/*`: if the package use `autoconf`. - and many more language specific package like `go/build`, etc... There are many pre-defined variables which you can use throughout your pipeline like: ``` ${{package.name}} ${{package.version}} ${{package.full-version}} ${{package.epoch}} ${{package.description}} ${{targets.destdir}} ${{targets.contextdir}} ${{targets.subpkgdir}} ${{host.triplet.gnu}} ${{host.triplet.rust}} ${{cross.triplet.gnu.glibc}} ${{cross.triplet.gnu.musl}} ${{build.arch}} ``` One of the most important one you should take note is the `${{targets.destdir}}` one. If you want a file to appear in your package tar, this is where you should add it. As @kaniini helps pointing out that `${{package.contextdir}}` is a new better var, which were [recently added Aug 2023](https://github.com/chainguard-dev/melange/pull/622). > This is intended to be used as a destination target instead of `${{targets.destdir}}` and `${{targets.subpkgdir}}`, allowing pipelines to be more reusable in subpackages, without hacks like ${{inputs.subpackage}}. For example, after build, my binary is located at `./dist/my-binary`, I will need to use this command below to add it to my package and have it installed at `/usr/local/bin` ```sh mkdir -p ${{targets.contextdir}}/usr/local/bin/ mv ./dist/my-binary ${{targets.contextdir}}/usr/local/bin/my-binary ``` You can also perform a quick check with `tar` to see what files your packages have with this ```sh tar --list -f /path/to/the/package/apk ``` ### Subpackages It's common practices where you will split up your packages into several packages to reduce the size of the main package. For example, you can split documentation into a subpackage like `my-pkg-doc`. This section looks pretty much like the main package's pipeline. ```yaml - name: bind-dnssec-tools dependencies: runtime: - bind-tools - libgcrypt - libxml2 - curl pipeline: - runs: | mkdir -p ${{targets.subpkgdir}}/usr/bin mv \ ${{targets.destdir}}/usr/bin/nsec3hash \ ${{targets.destdir}}/usr/bin/dnssec* \ ${{targets.subpkgdir}}/usr/bin/ description: Utilities for DNSSEC keys and DNS zone files management ``` ### Auto-udpate One of the selling point of Wolfi is they have a lot of automation built around the repo. One of it is this `update` section. In here you can define how you can hint the `wolfictl` bot to automate updating the package. It can either be from GitHub, Release Monitor or other sources. You can find out more in [wolfictl repo](https://github.com/wolfi-dev/wolfictl) ```yaml update: enabled: true release-monitor: identifier: 242117 ``` ## Testing the package I usually just use `apko` to test installing the package I recently build. You can create a file named `myimage.apko.yaml` with the following content: ```yaml contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://packages.wolfi.dev/os - '@local ./packages' packages: - wolfi-baselayout - busybox - bash - my-package@local entrypoint: command: /bin/sh archs: - amd64 ``` You will need to run `melange keygen` command once to generate the keypair. From the dev-container, you can simply run the following command ```sh apko build \ --keyring-append /etc/apk/keys/wolfi-signing.rsa.pub \ --keyring-append ./local-melange.rsa.pub --arch host \ myimage.apko.yaml image:tag image.tar # docker load -i image.tar # docker run -it --rm image:tag-amd64 ``` Once it's done, you can use `docker` to load it and run it. ## Where do I start? Scan Wolfi repo for [issue with label [wolfi-package-request]](https://github.com/wolfi-dev/os/issues?q=is%3Aopen+is%3Aissue+label%3Awolfi-package-request) is how I usually do. Or you can start with the package that you actually need. For reference build instruction, I would usually look at other packages within the Wolfi repo as well as the Alpine package registry. The Alpine package repository provides excellent instruction on how packaging should be done. You can take those as reference and start converting it into melange package. Here's the [link to Alpine packages registry](https://pkgs.alpinelinux.org/packages). You can search for the package, click on the link to go to package page. And then go to Git repository of that package. From there, click to view the `APKBUILD` file. ![](/img/alpine-package.png) If you struggle with any of the build, just open an issue and I'll be happy to help you out. --- --- layout: post title: "Almost** instant k8s secret update for your application" description: "How to almost instantly update k8s in your application" date: "2023-03-03T00:00:00Z" image: ["/img/k8s-secret-update.png"] thumbnail: "/img/k8s-secret-update.png" --- [Thinh Nguyen có chia sẻ 1 bài khá tổng quan về secret trên k8s](https://www.facebook.com/groups/cloudnativevietnam/posts/1544543652723407/?comment_id=1544648119379627¬if_id=1677938654238399¬if_t=feedback_reaction_generic&ref=notif). Mình thấy gãi đúng chỗ ngứa vấn đề của mấy năm trước nên gõ nhanh bài này chia sẻ cùng mọi người. ## The problem Có 1 vấn đề từ những ngày đầu của k8s (những năm 2014-2016) là khi configmap/secret đc update thì làm sao để application nhận được các thay đổi đó. Giải pháp thời ấy đơn giản nhất là tự đi mà rolling update pods. Sau này đỡ cực hơn chút là xài [stakater/Reloader](https://github.com/stakater/Reloader). Reloader là 1 controller tự động watch các thay đổi của configmap/secrets và rolling update lần lượt các pod tương ứng. Nếu bạn cảm thấy như vậy là xong thì chắc ko cần đọc tiếp :D Mục đích của bài này là làm thế nào mà ko cần phải rolling update các pods mà pods vẫn nhận được các thay đổi configmap/secrets **1 cách nhanh nhất có thể**? Nhanh hơn cả giải pháp dùng reloader trên kia và ko disruptive (ko cần rolling update). Mình cùng đi tìm hiểu nhé. Để giải quyết vấn đề này, chúng ta cần giải quyết được 2 vấn đề nhỏ: 1. Làm sao để thay đổi được reflect vào trong pod 2. Làm sao để application được alert và reload configuration mỗi khi có các thay đổi đó. ## Vấn đề số 1 Về lý thuyết thì kubelet, mình lấy ví dụ cụ thể ở đây là secret manager chẳng hạn, nó watch thẳng cái secret thì khi nào secret được update thì kubelet sẽ biết ngay luôn rồi. 1. Vậy tại sao lại có delay để secret được update trong pod??? Đơn giản là vì k8s ko impelement cơ chế nào để "sync" lại mounted secret volume ngay lập tức. 2. Vậy khi nào thì k8s/configmap được update trong pod? Khi nào mà pod cần update vì bất kì lý do nào đó. hoặc sau 1 khoảng thời gian **không cố định**. Vì sau lại là 1 khoảng mà ko cố định? Đó là vì default `resyncInterval` là `60s` và [`workerResyncIntervalJitterFactor`](https://github.com/kubernetes/kubernetes/blob/v1.26.0/pkg/kubelet/pod_workers.go#L231) là `0.5`. Đó là lý do bạn thấy khoảng thời gian lúc đo thực tế bị dao động từ 60 tới 90 giây. ### Giờ chúng ta biết cách thức secret/configmap được update vào trong pod như thế nào rồi, làm thế nào để nó nhanh hơn nhỉ? Cái này là 1 mẹo đơn giản mình có POC thời ở Techcombank. Nhớ đoạn trên mình viết là nếu bất cứ pod event nào thì nó sẽ trigger lại đoạn "sync" kia ko? Chúng ta cần 1 event nào đó mà ko rolling update pod (vì nếu rolling update thì mình làm kiểu này làm quái gì, xài Reloader cho xong). Hmmm, vậy update labels hoặc annotations là được chứ gì. Vì labels còn dành cho 1 số mục đích khác nên mình chọn annotations. Giải pháp là mình sử dụng 1. Viết controller để watch secret/configmap. Cái này mình dùng Kyverno luôn cho tiện vì lúc đó ở Techcombank đang triển khai Kyverno. Thay vì phải viết 1 cái controller đơn giản để watch secret/configmap cho việc này. 2. Setup 1 policy để mutate các pod tương ứng mỗi khi các secret/configmap đang dùng đc update. Add thêm 1 field là `foo: $randomValue` chẳng hạn. Mọi người có thể verify cái này bằng 1 app cực kì đơn giản print cái secret kia liên tục và sẽ thấy nó được update instantly sau khi bạn add annotation. ## Vấn đề số 2 Mình sẽ nói về các app chung chung, ko chỉ app deploy trên k8s nhé. Về việc làm sao để nhận secret mỗi khi có thay đổi thì các bạn dev đơn giản chỉ là watch cái file secret thôi (`inotify` syscall). Quá đơn giản phải ko, vài dòng code là xong. DONE. Mình cũng từng làm như vậy thời trước. Thế nhưng khi deploy lên k8s thì cơ chế ấy lại không hoạt động đúng và lỗi. Lý do thực ra lại rất đơn giản. Mỗi lần cái event "sync" kia được thực hiện để update secret/configmap thì cái file sẽ bị xóa đi và symlinked lại. Vì sao lại xóa đi, và vì sao lại là symlinked??? Mình cũng ko rõ đoạn này lắm nhưng nếu bạn mount secret vào trong pod thì bạn sẽ verify đc nó chỉ là symlink. Mỗi lần "sync" thì cái symlink kia sẽ được update, cái đích của symlink cũ sẽ bị xóa và bởi vậy trigger cái event file bị xóa đi ở trên. Lúc này thì đoạn code bình thường watch cái file của mình sẽ bị lỗi vì cái watch kia bị broken. Trong trường hợp này, mình chỉ việc update lại cái lib của mình để re-establish lại cái watch trong sự kiện file bị xóa kia thôi. ## Kết luận Đây là 1 vấn đề khá hay ho trong k8s và mình ko thấy nhiều người tìm hiểu sâu về việc này. Kiểu xài Reloader xong rồi là xong, cần gì phải tìm hiểu thêm ý. Nhưng nó thực sự rất thú vị nếu bạn tìm hiểu kĩ cách k8s hoạt động, cách linux system hoạt động, etc... Enjoy the weekend :) --- --- layout: post title: "Assume role without instance profile" description: "Assume role without instance profile? Is it even possible?" thumbnail: "/img/assume-role.jpg" date: "2023-02-20T00:00:00Z" --- ## Is it even possible? AWS Systems Manager có 1 tính năng là Default Host Management Configuration (DHMC). Cái này có gì hay ho? DHMC giúp bạn manage EC2 instances. Benefits bao gồm nhưng ko giới hạn những việc như: - Connect to your instances securely using Session Manager. - Perform automated patch scans using Patch Manager. - View detailed information about your instances using Systems Manager Inventory. - Track and manage instances using Fleet Manager. - Keep the SSM Agent up to date automatically. Nhưng hôm nay mình ko nói nhiều về nó. Mình vô tình đọc [đoạn code này](https://github.com/aws/amazon-ssm-agent/blob/a6135ec1c596d0b94f870e91eaa24b3fa9e8a8be/agent/agent_parser.go#L132) trong repo của ssm-agent. Nó có gì hay ho? ```go // registerManagedInstance checks for activation credentials and performs managed instance registration when present func registerManagedInstance(log logger.T) (managedInstanceID string, err error) { // try to activate the instance with the activation credentials publicKey, privateKey, keyType, err := registration.GenerateKeyPair() if err != nil { return managedInstanceID, fmt.Errorf("error generating signing keys. %v", err) } // checking write access before registering err = registration.UpdateServerInfo("", "", "", privateKey, keyType, "", registration.RegVaultKey) if err != nil { return managedInstanceID, fmt.Errorf("Unable to save registration information. %v\nTry running as sudo/administrator.", err) } // generate fingerprint fingerprint, err := registration.Fingerprint(log) if err != nil { return managedInstanceID, fmt.Errorf("error generating instance fingerprint. %v", err) } service := anonauth.NewClient(log, region) managedInstanceID, err = service.RegisterManagedInstance( activationCode, activationID, publicKey, keyType, fingerprint, ) if err != nil { return managedInstanceID, fmt.Errorf("error registering the instance with AWS SSM. %v", err) } err = registration.UpdateServerInfo(managedInstanceID, region, "", privateKey, keyType, "", registration.RegVaultKey) if err != nil { return managedInstanceID, fmt.Errorf("error persisting the instance registration information. %v", err) } // saving registration information to the registration file reg := map[string]string{ "ManagedInstanceID": managedInstanceID, "Region": region, } var regData []byte if regData, err = json.Marshal(reg); err != nil { return "", fmt.Errorf("Failed to marshal registration info. %v", err) } if err = ioutil.WriteFile(registrationFile, regData, appconfig.ReadWriteAccess); err != nil { return "", fmt.Errorf("Failed to write registration info to file. %v", err) } return managedInstanceID, nil } ``` Đoạn code ngắn này có gì đặc biệt? Nó giúp bạn đăng kí 1 instance trở thành managed instance của DHMC. Yêu cầu của việc này là gì? Yêu cầu là instance đó cần có access tới IMDS v2 để có thể get về instance identity credential. Sử dụng credential này, chúng ta có thể gọi sang System Manager để đăng kí trở thành managed instance. Đơn giản vậy thôi. Việc hay ho hơn nằm ở đoạn sau. Sau khi request trở thành managed instance, chúng ta có thể gọi request managed role token sử dụng instance identity credential và SSM key authorization ở bước trước. System Manager sẽ gọi sang STS để assume role vào role của DHMC và trả lại credential cho EC2 instance. Nếu bạn chú ý 1 chút trong docs có note lại là > If you prefer to use a custom role, the role's trust policy must allow Systems Manager as a trusted entity. Việc bên trên chính là lý do vì sao bạn phải làm như vậy :) Đôi khi, nếu đọc docs kĩ 1 chút, bạn sẽ hiểu rõ hơn đằng sau AWS làm những gì :) Vậy là bạn đã "assume role" đc vào DHMC role rồi đó, một cách không trực tiếp. ## Kết luận Mình chỉ hơi ngạc nhiên là việc đăng kí trở thành managed instance lại dễ dàng như vậy. Mặc dù ai cũng hiểu khi enable DHMC lên thì chỉ gắn 1 role minimal thôi. Việc này cũng như kiểu 1 club mà ai cũng có thể đăng kí hội viên và người đó có thể trở thành hội viên ngay lập tức và có tất cả các đặc quyền của hội viên mà ko phải xác nhận gì cả. --- --- layout: post title: "CS:GO launch failure fix" description: "CS:GO launch failure fix" thumbnail: "/img/csgo-player.jpg" date: "2022-03-18T00:00:00Z" --- The symptom: CS:GO launched to a black screen and just crash right after. The OS I'm using is Pop!_OS 21.10. The fix here is ```sh sudo apt install libtcmalloc-minimal4 cd /steamapps/common/Counter-Strike\ Global\ Offensive/bin/linux64 # backup the old file mv libtcmalloc_minimal.so.0 libtcmalloc_minimal.so.0.bak mv libtcmalloc_minimal.so.4 libtcmalloc_minimal.so.4.bak ln -s /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 libtcmalloc_minimal.so.0 ln -s /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 libtcmalloc_minimal.so.4 ``` The game should be able to launch after this. However, if you do "verify integrity of game files", you will need to do this fix again. --- --- date: "2021-12-27T00:00:00Z" title: "Public ECR and private subnets" thumbnail: "/img/aws-ecr-bandwidth.jpg" --- Hôm trước mình hơi băn khoăn chút việc nếu sử dụng public ECR (public.ecr.aws) với sử dụng 1 k8s cluster trong private subnets, thì bandwidth từ việc pull images về trong private subnets sẽ đc tính thế nào. 1. Tính vào cho NAT gateway? 2. Hay tính vào cho S3 Gateway Endpoint? Vì public ECR sử dụng S3 làm storage nên trong trường hợp ngon nghẻ nhất là bandwidth đc tính vào Gateway endpoint thì ngon :) ## k8s pull images thế nào? mặc định thì kubelet sẽ pull image tùy theo pull policy. trong trường hợp image ko có thì sẽ request lên registry. container registry sẽ provide binary images hoặc [container image index](https://github.com/opencontainers/image-spec/blob/main/image-index.md) (trong trường hợp sử dụng nhiều [image manifests](https://github.com/opencontainers/image-spec/blob/main/manifest.md) cho multi-arch). Trong mỗi image manifest này sẽ có thông tin về image bao gồm những layers nào: `mediaType`, `size`, `digest` như thế nào. Từ digest này, kubelet sẽ gọi có thể lấy đc layer đó về qua registry API `GET /v2//blobs/`. Cho tới đây thì chúng ta biết đc là kubelet sẽ pull image về như thế nào rồi đúng ko? ## Cách tính bandwidth cho NAT hay cho S3 gateway endpoint thì sao? Giả sử như public ECR trả về 1 cái pre-signed S3 URL thì mọi chuyện sẽ rất đơn giản đúng ko? Nhưng trong image index spec thì ko có cái đó và các layers đc serve thẳng từ registry API. Quay lại việc S3 endpoint hoạt động như thế nào. Đơn giản là việc add thêm IP range của S3 vào trong network route table thế nên nếu cái endpoint kia resolve ra đc thì sao? Câu trả lời là cũng ko đc. Cái registry URL này ko thể resolve ra IP range của VPC endpoint đc (để chúng ta có thể route nó qua vpc endpoint) vì nó là dùng chung của registry và còn serve các API khác nữa của registry nữa. Thêm 1 điểm nữa để confirm là khi chúng ta dig `public.ecr.aws` thì ra `awsglobalaccelerator.com`. Cái này càng ko thể đc phép resolve ra IP range của VPC endpoint của từng service đc. ``` dig @1.1.1.1 public.ecr.aws ; <<>> DiG 9.16.15-Ubuntu <<>> @1.1.1.1 public.ecr.aws ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2555 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;public.ecr.aws. IN A ;; ANSWER SECTION: public.ecr.aws. 300 IN CNAME a961edf72200aa9b1.awsglobalaccelerator.com. a961edf72200aa9b1.awsglobalaccelerator.com. 300 IN A 99.83.145.10 a961edf72200aa9b1.awsglobalaccelerator.com. 300 IN A 75.2.101.78 ;; Query time: 155 msec ;; SERVER: 1.1.1.1#53(1.1.1.1) ;; WHEN: Tue Dec 28 17:37:38 +07 2021 ;; MSG SIZE rcvd: 131 ``` ## Thế kết luận là? Thôi thì ngồi đoán mãi cũng chỉ tới vậy. Đơn giản nhất là spun up 1 instance lên rồi pull vài chục lần cái image to là đc mà phải ko :) Có vẻ là nó đc tính cho NAT bandwidth từ cloudwatch metrics lúc mình test (trừ khi mình setup test sai =)). Và kết luận này có vẻ cũng hợp lý vì nếu bỏ NAT đi thì ko pull đc từ private subnets nữa. --- --- date: "2021-09-21T00:00:00Z" title: Shure MV7 working on Linux description: "tldr: Shure MV7 is working great on Linux" thumbnail: "/img/shure-mv7.jpg" --- I read many posts regarding Shure MV7 doesn't work at all on Linux: [here](https://www.reddit.com/r/podcasting/comments/jrgpbv/recommendations_for_shure_mv7_with_linux/), [here](https://askubuntu.com/questions/1289752/ubuntu-support-for-shure-mv7-usb-microphone) and [here](https://forum.manjaro.org/t/audio-card-disconnects-when-audio-is-playing/35021). It got me nervous as I already bought this on Amazon and return is not an option since I live in Vietnam. However, when I receive the mic today, it just works, right out of the box, on Pop!_OS (v 21.04). The only thing different with the vanilla Pop!_OS distro is I switch to pirewire for audio instead of pulseaudio. For those with the mic and struggle to get it to work, I strictly follow the setup instruction [post here on Reddit](https://www.reddit.com/r/pop_os/comments/ofdalv/replaced_pulseaudio_with_pipewire_on_popos_2104_i/h4c5p6u/). Many thanks to bob488. --- --- date: "2021-09-03T00:00:00Z" tags: - Kubernetes - Docker title: Using k8s kind "rootlessly" without Docker description: "When I tried to use kind in rootless mode with nerdctl" thumbnail: "/img/k8s.jpg" --- So you probably already heard the news [Docker Desktop is no longer free](https://www.theregister.com/2021/08/31/docker_desktop_no_longer_free/). While this mostly affect macOS and Windows users and I use Pop!_OS, I still would like to see if we can get by without Docker at all. I've been using nerdctl for quite awhile now and while [nerdctl](https://github.com/containerd/nerdctl) mostly fill my needs for `docker` CLI, I "kinda" need `kind` CLI to create test cluster for testing purpose. However `kind` still needs `docker`. What if I alias nerdctl to docker? I did that and then try again ```sh ln -s nerdctl docker kind create cluster --name test ``` Now I'm getting different error. ``` ERROR: failed to create cluster: running kind with rootless provider requires cgroup v2, see https://kind.sigs.k8s.io/docs/user/rootless/ ``` Well, this is good right? I just have to enable cgroup v2 and then I should be good to go? Usually I do have cgroup v2 enable but I'm trying Pop!_OS at the moment and the kernel is kinda old. So I upgrade kernel to the latest stable (5.13), using a custom kernel by Xanmod. ```sh uname -a Linux x300 5.13.14-xanmod1 #0~git20210903.d548864 SMP PREEMPT Fri Sep 3 13:21:07 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux ``` The steps are a bit different when I was using Manjaro but it basically boil down - Adding a new kernel parameter `systemd.unified_cgroup_hierarchy=1`. The instruction on kind sig page doesn't work for me. On Pop!_OS, I need to use `kernelstub` - Delegate a few more controllers, namely `cpu`, `cpuset` and `io`. ```sh sudo kernelstub -a "systemd.unified_cgroup_hierarchy=1" sudo reboot ``` After that, I'm getting a tiny bit different error ``` ERROR: failed to create cluster: running kind with rootless provider requires setting systemd property "Delegate=yes", see https://kind.sigs.k8s.io/docs/user/rootless/ ``` This looks like, because only `memory` and `pids` controllers are delegated to non-root users but we need more, specially `cpu`, `cpuset` and `io` controllers. We can verify this by, the following command. You will see only `memory` and `pids` are delegated. ```sh cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers memory pids ``` You can delegate more by doing this, and verify with the above command. ``` # mkdir -p /etc/systemd/system/user@.service.d # cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF [Service] Delegate=cpu cpuset io memory pids EOF # systemctl daemon-reload ``` If all is good, this is what you see ``` cat /sys/fs/cgroup/user.slice/user-(id -u).slice/user@(id -u).service/cgroup.controllers cpuset cpu io memory pids ``` I thought it should be ok now but no, I still got the above error ``` ERROR: failed to create cluster: running kind with rootless provider requires setting systemd property "Delegate=yes", see https://kind.sigs.k8s.io/docs/user/rootless/ ``` At this point, I decided to jump into kind codebase to see the condition that trigger that error. Turns out, they use `docker info` command to see if cgroup v2 is active and to see what kind of controllers got delegated. And `nerdctl` doesn't emit those info yet. `nerdctl info` looks like below and the docker one has a lot more information regarding where cpushare is supported, pid is supported, etc... ```json { "ID": "86232191-2d46-475b-be0c-1472c5174763", "Driver": "overlayfs", "Plugins": { "Log": [ "json-file" ], "Storage": [ "native", "overlayfs" ] }, "LoggingDriver": "json-file", "CgroupDriver": "systemd", "CgroupVersion": "2", "KernelVersion": "5.13.14-xanmod1", "OperatingSystem": "Pop!_OS 21.04", "OSType": "linux", "Architecture": "x86_64", "Name": "x300", "ServerVersion": "v1.5.5", "SecurityOptions": [ "name=seccomp,profile=default", "name=cgroupns", "name=rootless" ] } ``` So at this point, I can only [log the issue on `nerdctl` repo](https://github.com/containerd/nerdctl/issues/349) and see if it's really the only problem or there would be sth else that prevent `kind` working with `nerdctl`. **Update:** So I tried to fix `nerdctl info` command and once I did, I got another error regarding `nerdctl ps` where `--filter` flag is not yet implemented. So thí is where I stopped for now. I will revisit this later. ``` ERROR: failed to create cluster: failed to list clusters: command "docker ps -a --filter label=io.x-k8s.kind.cluster=test --format '{{.Names}}'" failed with error: exit status 1 Command Output: Incorrect Usage: flag provided but not defined: -filter ``` --- --- date: "2021-08-19T00:00:00Z" tags: - Kubernetes title: Làm quen với Pod Security Admission (PSA) thumbnail: "/img/nodejs-rust.jpg" --- K8s 1.22 giới thiệu Pod Security Admission (sau này gọi tắt là PSA) phiên bản alpha, để thay thế cho Pod Security Policy (PSP). Bài viết này sẽ hướng dẫn qua cách bạn setup PSA và sử dụng PSA 1 cách cơ bản nhất. ## Enable PSA Để cho mục đích lab đơn giản, mình sẽ sử dụng `kind` để tạo 1 cluster local. Mình sẽ tạo 1 cluster và enable PSA lên với config như sau Chú ý chút chỗ mình enable `PodSecurity` trong `featureGates` sang `true`. ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: PodSecurity: true nodes: - role: control-plane - role: worker ``` Tạo cluster với lệnh dưới đây ```sh kind create cluster \ --image=kindest/node:v1.22.0@sha256:b8bda84bb3a190e6e028b1760d277454a72267a5454b57db34437c34a588d047 \ --config kind-config.yaml ``` Tạo thành công, bạn sẽ thấy log kiểu như vậy ```sh Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:v1.22.0) 🖼 ✓ Preparing nodes 📦 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 ✓ Joining worker nodes 🚜 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Have a nice day! 👋 ``` Giờ chúng ta đã có 1 cluster đc enable PSA sẵn sàng để vọc. ## Vọc Có 3 loại policies cơ bản - Privileged: mở, và ko recommend xài - Baseline: cơ bản - Restricted: chặt nhất và là best practices. 3 loại policies này sẽ đc áp dụng ở 1 trong 3 mode sau (khá là self-explained rồi nên mình ko đi sâu nữa). - enforce - audit - warn Các loại policies này sẽ siết chặt việc mình sử dụng 1 số fields trong Pod specs như container ports, hostPath và securityContext. ## Áp policy lên namespace Để preview thì mình xài `dry-run` flag trước: apply baseline warn mode và warn ver 1.22 lên default namespace ```sh kubectl label --dry-run=server --overwrite ns default \ pod-security.kubernetes.io/warn=baseline \ pod-security.kubernetes.io/warn-version=v1.22 ``` Thường thì cái namespace này mới cài nên ko có gì. Để thử cho vui thì mình sẽ thử tiếp dry-run với tất cả namespace và enforce mode xem thế nào. ```sh kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=baseline ``` H thì bạn sẽ thấy 1 số warning như sau. Cái này là expected behavior vì apiserver, etcd control plane, kube-proxy cần những cái đó. Nothing to alarm here. ```sh namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: kindnet-s4b45: non-default capabilities, host namespaces, hostPath volumes Warning: kube-apiserver-kind-control-plane: host namespaces, hostPath volumes Warning: etcd-kind-control-plane: host namespaces, hostPath volumes Warning: kube-controller-manager-kind-control-plane: host namespaces, hostPath volumes Warning: kube-scheduler-kind-control-plane: host namespaces, hostPath volumes Warning: kube-proxy-m69hp: host namespaces, hostPath volumes, privileged Warning: kube-proxy-gcm2c: host namespaces, hostPath volumes, privileged Warning: kindnet-pvg4x: non-default capabilities, host namespaces, hostPath volumes namespace/kube-system labeled namespace/local-path-storage labeled ``` ## Áp dụng cho cả cluster thì sao bạn cần config thêm cho API server, thêm path tới configuration dưới đây cho flag `--admission-control-config-file` Đây là 1 ví dụ bạn bật baseline ở mode enforce lên tất cả namespaces trừ `kube-system` ```yaml apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: DefaultPodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1alpha1 kind: PodSecurityConfiguration defaults: enforce: "baseline" enforce-version: "latest" exemptions: usernames: [] runtimeClassNames: [] namespaces: [kube-system] ``` Để start thì bạn có thể bắt đầu với baseline. Sau đó có thể build thêm các policies thêm tùy theo nhu cầu của từng tổ chức :) Happy hacking k8s :) --- --- date: "2021-08-14T00:00:00Z" title: My blogging setup these days thumbnail: "/img/blogging.jpg" --- ## Previous setup I used a small VPS instance on RamNode to host my blog previously. No particular reason. I just happened to have lots of unused credits there. I have a local git repo on my Macbook. Setup a git hook to trigger `jekyll` build on the VPS. Nothing fancy. No CI/CD whatsoever. ## The new setup I recently migrated my blog from self-hosted on RamNode to Cloudflare Pages. There are still some quirks but it's alright for personal use. I hosted the source code of my blog on GitHub. They already have a nice Markdown editor with preview which I can use from anywhere via a web browser. I can just create a new file in `_posts`, preview with GitHub editor and when I'm done with it, commit and push. Cloudflare Pages will take it from there. This is important because these days, I'm mostly using my iPad for all of my computational needs. And the web browser on iPad is desktop grade now :). Prior this, I tried another setup where I setup [code-server](https://github.com/cdr/code-server) on a local machine at home and use vscode via browser. To access the code-server from the Internet while I'm not at home, I installed Tailscale on that machine and my iPad. Btw, Tailscale is amazing. You ought to try it out. While the code-server setup works and quite extensible, I feel it's still too heavy for blogging needs. I still keep this setup around when I need to tinker with Golang or Rust. --- --- date: "2021-08-13T00:00:00Z" tags: - Programming title: How to write Node modules with Rust thumbnail: "/img/nodejs-rust.jpg" --- TLDR: there's a [sample repo here](https://github.com/tuananh/node-module-rust-example) if you're lazy to read this post. The sample repo include GitHub Actions sample for CI as well. ## The Rust bit It's very simple. You write your function in Rust ```rs #[js_function(3)] fn say_hello(ctx: CallContext) -> Result { let name = ctx.get::(0)?.into_utf8()?; let name = name.as_str()?; let s = ctx.env.create_string_from_std(format!("Hello, {}!", name))?; Ok(s) } ``` And then you expose it to Node.js runtime with ```rs #[module_exports] fn init(mut exports: JsObject) -> Result<()> { exports.create_named_method("say_hello", say_hello)?; Ok(()) } ``` ## Build You then build it with `napi` by using this command `napi build --platform` And if you want to build for multi-platform, you will have multiple binaries, so you need to load accordingly in Node.js. We use `detect-libc` library for this. ```js let parts = [process.platform, process.arch]; if (process.platform === 'linux') { const {MUSL, family} = require('detect-libc'); if (family === MUSL) { parts.push('musl'); } else if (process.arch === 'arm') { parts.push('gnueabihf'); } else { parts.push('gnu'); } } else if (process.platform === 'win32') { parts.push('msvc'); } module.exports = require(`./node-module-rust-example.${parts.join('-')}.node`); ``` And that's it. You can replace `test.js` with your favorite test runner. The test code there is just dummy code. --- --- date: "2021-06-04T00:00:00Z" title: What the fuck is even GitOps thumbnail: "/img/gitops.jpg" --- ## Nguồn gốc 2017, term GitOps đc WeaveWorks promote lên với bài viết ["Operations by Pull Request"](https://www.weave.works/blog/gitops-operations-by-pull-request), đại để là - k8s system state đc lưu ở 1 git repo - changes made thông qua pull request rồi chạy CI/CD pipeline - có công cụ hỗ trợ detect configuration drift và reconciler. Nhìn qua thì cũng chẳng có gì đặc biệt vì từ 2016, khi bên mình triển khai Kubernetes, mình cũng đã CI/CD và store system state ở git repo rồi. Chẳng qua là thiếu phần drift detector và reconciler thôi là đc gọi là GitOps rồi ;)) Nếu ai quan tâm xem GitOps thực sự như thế nào thì có thể tham khảo thêm [repo của GitOps working group](https://github.com/gitops-working-group/gitops-working-group). ## Thế thực sự thì GitOps có gì mới Thực sự là với các bạn DevOps thì GitOps chẳng có gì mới cả. Nếu bạn đã làm việc với infrastructure as code thì các định nghĩa bên trên cũng đều có đủ cả. Kết hợp với CI/CD thì cả 2 chẳng qua cũng chỉ là 1 ý tưởng nhưng scope hơi khác nhau 1 chút. 1 bên là infrastructure config + state và 1 bên là application workload state. Với mình, GitOps chỉ là 1 trend ko hơn ko kém đc promoted bởi WeaveWorks. Kể cả GitOps working group cũng bị influenced bởi WeaveWorks khá nhiều. Cộng với việc k8s popularity tăng nhanh thì việc đú trend GitOps trở nên hot hơn bao giờ hết. ## Mình có cần GitOps ko? Câu trả lời là tùy :D. Không phải bộ phận nào trong tổ chức của bạn cũng familiar với Git. Nếu tổ chức của bạn ko phải là engineering driven thì có lẽ là ko nên xài. Nên hiểu sâu về cách thức, điểm lợi, hại của từng approach mà áp dụng phù hợp với tổ chức của bạn. --- --- date: "2021-04-12T00:00:00Z" title: Distributed tracing is the new structured logging thumbnail: "/img/distributed-tracing.jpg" --- ## Structured logging Một best practice vẫn được recommend cho tới bây giờ là structured logging. Structured log là 1 dạng logging theo kiểu `key=val` để có thể giúp chúng ta dễ dàng parse log và đưa vào 1 log store để tiện query và phân tích. ```js logger.info({ request_time: 1000, payload_size: 2000 }) ``` sau đó 1 đoạn structured log sẽ đc generated ra kiểu này, ngoài các metadata chúng ta log thì còn đi kèm thêm 1 số metadata khác như `timestamp`, `hostname`, `deployment name`, `pod name`, etc.. tùy vào cách chúng ta muốn annotate thêm gì ``` {"level":30,"time":1531171082399,"pid":657,"hostname":"x300","request_time":1000,"payload_size":2000} ``` Nhìn qua thì chúng ta có thể thấy structured logging giống như việc chúng ta define 1 bảng quan hệ (relational table) hoặc 1 schema. Nhưng việc structured logging giống với 1 bảng quan hệ thì cũng ko nên nhét chúng vào 1 database quan hệ nha. Cái đó sẽ là thảm họa nếu app của bạn log nhiều. Cho tới lúc này, chúng ta có thể làm các việc kiểu filter/query hoặc aggregate dữ liệu log như SQL được. Trừ việc JOIN để lấy dữ liệu của query context. Tới đây thì mọi việc vẫn tạm ổn đúng ko? ## Come microservices!! Mọi việc vẫn ổn cho tới khi chúng ta đổi sang kiến trúc microservices. Với microservice, chúng ta chỉ có 1 phần của bức tranh (context) mà chỉ available ở microservice đó thôi. Nếu muốn log thêm thì sao? Ví dụ: nếu bạn cần log ra các experiment flags trong context của request đó. Đơn giản đúng ko? Chúng ta chỉ cần pass thông tin đó qua cho microservice mà chúng ta cần phải ko? và giải quyết việc JOIN đó ở tầng app code khi chúng ta log thêm thông tin mà chúng ta vừa pass qua. ```js logger.info({ request_time: 1000, payload_size: 2000, experiment_flags: ['ABTEST_1', 'ABTEST_2'] }) ``` Nhưng việc làm thế này là cực kì tốn công sức maintain, dễ gây lỗi và ko thể scale đc. ## Distributed tracing to the rescue ![](/img/jaeger-demo.png) Distributed tracing nổi lên cùng thời điểm kiến trúc microservice đã khá mature. Khi mà mọi người đã hiểu rõ microservice hơn và các điểm hạn chế của nó (namely obversability). Và đây là lý do mình nói "distributed tracing is the new structured logging". Nó sẽ là 1 phần ko thể thiếu với microservice architecture và có thể thay thế (có thể ko hoàn toàn) cho structured logging. --- --- date: "2021-02-13T00:00:00Z" title: "The state of Linux on desktop (2020)" thumbnail: "/img/linux-desktop.jpg" --- I got fed up with macOS. While the new hardware(Apple Silicon) got amazing feedbacks, the OS itself is so lag behind. I got a Windows 10 desktop at home and heck, it was even much more pleasant to use than using macOS. - As a typical user (web browsing, mail and office stuff), Windows 10 is very good. - As a developer, it's getting a lot better with WSL/Microsoft Terminal/etc... I decided to give Linux another evaluation test. I pick Manjaro - an Arch-based over an Ubuntu-based distro this time after hearing all kind of praise from its users. But I also don't want to configure everything I need to use, hence Manjaro. > Manjaro is a user-friendly Linux distribution based on the independently developed Arch operating > system. Within the Linux community, Arch itself is renowned for being an exceptionally fast, > powerful, and lightweight distribution that provides access to the very latest cutting edge - and > bleeding edge - software. However, Arch is also aimed at more experienced or technically-minded > users. As such, it is generally considered to be beyond the reach of those who lack the technical > expertise (or persistence) required to use it. > > via [wiki.manjaro.org](https://wiki.manjaro.org/index.php/About_Manjaro) So looks like it got the best of both worlds right? ## The test setup I built a new mini PC recently. It's the Asrock Deskmini X300W that use AMD processor. If you prefer Intel, you can choose the Intel version of the box. I went with AMD because I like their Zen offering and I would love to support them. I just throw a 6 cores AMD 4650G processor, 32GB of 3200Mhz Crucial memory, 512GB Samsung NVME drive for OS and other stuff plus another 1TB 2.5' SSD for storage. For OS, I went with Manjaro KDE variant because I like the look of it. ## The experience Almost everything works out of the box. - The graphic works right. I do not have an Intel GPU so it's much easier for me but I hear terrifying stories from other side of the world. - WiFi works. Zero complaints here. - The bluetooth is almost ok. Most stuff I throw at it works, except an old Xbox One controller of mine. The one came with Xbox One S works with 1 minor additional step (disable ERTM). I tested with 4 bluetooth mouses, 2 keyboards, 1 speaker and 2 Xbox controllers. - Since I pick KDE, it's a bit troublesome to use setup i3 wm. After reading several tutorials, I decided not to bother with one. Instead, I settled with [krohnkite](https://github.com/esjeon/krohnkite) plugin for KWin. It works really well for my needs , given that my needs are pretty basic. - I do gaming once in awhile and Manjaro even came bundled with Steam (LOL). One might say it's so bloat but I'm ok. Storage is cheap these days. - Developer experience is awesome. Linux is usually first-class platform for open source projects. Everything just works. Docker is so fast because no VM required. It's the best platform for developers, hands down. ## Conclusion So far, I'm loving it. It does everything I need and works with all the peripherals I have, with the exception of the Xbox One controller (wired connection still work though). I'm gonna stick with Manjaro for now. I don't see myself moving to Arch since my love for tweaking the system is long gone. I just want something that works and Manjaro does work very well for me. --- --- date: "2021-02-09T00:00:00Z" link: https://github.com/ViRb3/wgcf title: Using Cloudflare Warp on Linux thumbnail: "/img/cloudflare-warp.jpg" --- Cloudflare Warp is currently not supporting Linux. However, since it's just Wireguard underneath, we can still use it unofficially. ## Install wgcf and wireguard-tools - Get `wgcf` from [its repo](https://github.com/ViRb3/wgcf). - Install `wireguard-tools`. I use Manjaro so I will use pacman for this `pacman -S wireguard-tools`. ## Generate Wireguard config You can now use `wgcf` to register, and then generate Wireguard config. ``` wgcf register wgcf generate ``` - `register` command will create a file named `wgcf-account.toml`. - `generate` command will generate wireguard config file named `wgcf-profile.conf`. ## Usage Now, copy the generated profile over to `/etc/wireguard` and use `wg-quick` utility to simplify setting wireguard interface. ```bash sudo cp wgcf-profile.conf /etc/wireguard wg-quick up wgcf-profile ``` Verify it's working with `wgcf trace` or navigate to this page: [https://www.cloudflare.com/cdn-cgi/trace](https://www.cloudflare.com/cdn-cgi/trace). The output should have `warp: on`. --- --- date: "2020-06-01T00:00:00Z" description: An extremely fast streaming SAX parser for Node.js, written in C++ image: "" link: https://github.com/tuananh/sax-parser tags: - Open source title: An extremely fast streaming SAX parser for Node.js thumbnail: "/img/sax-parser.jpg" --- *TLDR: I wrote a SAX parser for Node.js. It's available here on GitHub : [https://github.com/tuananh/sax-parser](https://github.com/tuananh/sax-parser)* I got asked about complete XML parsing with `camaro` from time to time and I haven't yet managed to find time to implement yet. Initially I thought it should be part of `camaro` project but now I think it would make more sense as a separate package. The package is still in alpha state and should not be used in production but if you want to try it, it's available on npm as [`@tuananh/sax-parser`](https://www.npmjs.com/package/@tuananh/sax-parser). ## Benchmark The initial benchmark looks pretty good. I just extract the benchmark script from `node-expat` repo and add few more contenders. ``` sax x 14,277 ops/sec ±0.73% (87 runs sampled) @tuananh/sax-parser x 45,779 ops/sec ±0.85% (85 runs sampled) node-xml x 4,335 ops/sec ±0.51% (86 runs sampled) node-expat x 13,028 ops/sec ±0.39% (88 runs sampled) ltx x 81,722 ops/sec ±0.73% (89 runs sampled) libxmljs x 8,927 ops/sec ±1.02% (88 runs sampled) Fastest is ltx ``` `ltx` package is fastest, win by almost 2 (~1.8) order of magnitude compare with the second fastest (@tuananh/sax-parser). However, `ltx` is not fully compliant with XML spec. I still include `ltx` here for reference. If `ltx` works for you, use it. | module | ops/sec | native | XML compliant | stream | | ------------------- | ------- | ------ | ------------- | ------ | | node-xml | 4,335 | ☐ | ✘ | ✘ | | libxmljs | 8,927 | ✘ | ✘ | ☐ | | node-expat | 13,028 | ✘ | ✘ | ✘ | | sax | 14,277 | ☐ | ✘ | ✘ | | @tuananh/sax-parser | 45,779 | ✘ | ✘ | ✘ | | ltx | 81,722 | ☐ | ☐ | ✘ | ## API The API looks simply enough and quite familiar with other SAX parsers. In fact, I took the inspiration from them (`sax` and `node-expat`) and mostly copied their APIs to make the transition easier. An example of using `@tuananh/sax-parser` to prettify XML would be like this ```js const { readFileSync } = require('fs') const SaxParser = require('@tuananh/sax-parser') const parser = new SaxParser() let depth = 0 parser.on('startElement', (name) => { let str = '' for (let i = 0; i < depth; ++i) str += ' ' // indentation str += `<${name}>` process.stdout.write(str + '\n') depth++ }) parser.on('text', (text) => { let str = '' for (let i = 0; i < depth + 1; ++i) str += ' ' // indentation str += text process.stdout.write(str + '\n') }) parser.on('endElement', (name) => { depth-- let str = '' for (let i = 0; i < depth; ++i) str += ' ' // indentation str += `<${name}>` process.stdout.write(str + '\n') }) parser.on('startAttribute', (name, value) => { // console.log('startAttribute', name, value) }) parser.on('endAttribute', () => { // console.log('endAttribute') }) parser.on('cdata', (cdata) => { let str = '' for (let i = 0; i < depth + 1; ++i) str += ' ' // indentation str += `` process.stdout.write(str) process.stdout.write('\n') }) parser.on('comment', (comment) => { process.stdout.write(`\n`) }) parser.on('doctype', (doctype) => { process.stdout.write(`\n`) }) parser.on('startDocument', () => { process.stdout.write(`\n`) }) parser.on('endDocument', () => { process.stdout.write(``) }) const xml = readFileSync(__dirname + '/../benchmark/test.xml', 'utf-8') parser.parse(xml) ``` --- --- date: "2020-05-19T00:00:00Z" description: 'Breaking changes in camaro v6: require Node 12 or newer. Major performance improvement.' image: "" tags: - Open source title: camaro v6 thumbnail: "/img/sax-parser.jpg" --- I recently discover [piscina](https://github.com/jasnell/piscina) project. It's a very fast and convenient Node.js worker thread pool implementation. Remember when `worker_threads` first introduced, the worker startup is rather slow and pool implementation is generally advised. However, there wasn't any good enough implementation yet until `piscina`. Since v4 when I move to WebAssembly, camaro performance took a huge hit (3 folds) and I was still trying to find a way to fix this perf regression. Well, `piscina` (`worker_threads`) seems to be the answer to that. Take a look at `piscina` example: ```js const Piscina = require('piscina'); const piscina = new Piscina({ filename: path.resolve(__dirname, 'worker.js') }); (async function() { const result = await piscina.runTask({ a: 4, b: 6 }); console.log(result); // Prints 10 })(); ``` and `worker.js` ```js module.exports = ({ a, b }) => { return a + b; }; ``` Sure it looks simple enough so I wrote a quick script to wrap `camaro` with `piscina`. And the performance improvement is sweet: it's about five times faster (ops/sec) and the CPU on my laptop is stressed nicely. ``` camaro v6: 1,395.6 ops/sec fast-xml-parser: 153 ops/sec xml2js: 47.6 ops/sec xml-js: 51 ops/sec ``` More importantly, it scales nicely with CPU core counts, which `camaro` v4 with WebAssembly isn't. ![](/img/camaro-v6-benchmark.jpg) In order to use this, I would have to drop support for Node version 11 and older but the performance improvement of this magnitude should guarantee such breaking changes right? I published the first [alpha build to npm](https://www.npmjs.com/package/camaro/v/6.0.0-alpha.0) if anyone want to give it a try. --- --- date: "2020-05-17T00:00:00Z" description: How to migrate from Zsh to Fish shell image: /img/fish.jpg tags: - macOS title: From Zsh to Fish on macOS thumbnail: "/img/macos-fish.jpg" --- I recently give fish shell another try and it doesn't disappoint me this time. The support from various tools has improve tremendously and the ecosystem seesm to be a lot more mature last I tried. It tooks me like 15-20 minutes to migrate over everything to fish and it seems fish provides everything I need from zsh out of the box. Remind me why I need `oh-my-zsh` again? ## Installation Install via `homebrew` and set `fish` as default shell. ```sh brew install fish chsh -s (which fish) ``` To go back to `zsh`: do `chsh -s (which zsh)`. ## Migration `fish`'s configuration is located at `$HOME/.config/fish`. The equivalent of `.zshrc` or `.bashrc` is `config.fish` at `$HOME/.config/fish`. ### Sourcing The `source` command work just like normal. By default, `fish` will source from files in `$HOME/.config/fish/conf.d` folder automatically so you can put your aliases, functions, .. there. ## Fixing functions A typical function in `fish` looks like this. I take `gi` (gitignore) function as a simple example. Seems pretty straightforward and even more self-explain than in zsh. ```sh function gi -d "gitignore.io cli for fish" set -l params (echo $argv|tr ' ' ',') curl -s https://www.gitignore.io/api/$params end ``` ## Checking other stuff you use If there's no `fish` support from the tool you use, there's [bass](https://github.com/edc/bass) which add support for bash utilties from fish shell. Example with `nvm`: ```sh bass source ~/.nvm/nvm.sh --no-use ';' nvm use node # latest ``` However, using `bass` can make it quite slow in some cases. So if the tools you use do support `fish`, use it native functions. ## Package manager There are: - [fisher](https://github.com/jorgebucaran/fisher) - [oh-my-fish](https://github.com/oh-my-fish/oh-my-fish) - [fundle](https://github.com/danhper/fundle) I haven't actually check them all out. I just went with the first result I got (fisher) and it's working pretty well for the purpose. ## Disable welcome message ``` set fish_greeting ``` ## FAQs [The FAQs](https://fishshell.com/docs/current/faq.html) is very nice. Be sure to check it out. --- --- date: "2020-05-12T00:00:00Z" description: kubectl run generators removed from kubectl 1.18, except one for generating pod. image: /img/k8s-kubectl-run-removed.jpg tags: - Kubernetes title: kubectl run generators removed --- Đây là [merged pull request](https://github.com/kubernetes/kubernetes/pull/87077) liên quan. Tóm tắt lại, trước đây nếu cần tạo deployment, bạn chỉ cần kubectl run nginx --image=nginx:alpine --port=80 --restart=Always Tính năng này được sử dụng rất nhiều vì 1 minimal deployment YAML khá dài. Đây là ví dụ ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 ``` Trước đây, để tạo 1 deployment và expose thì chỉ cần đơn giản 2 lệnh là kubectl run nginx --image=nginx:alpine --port=80 --restart=Always kubectl expose deployment nginx --port=80 --type=LoadBalancer Bây giờ, bạn cần tự nhớ deployment YAML và expose nó với lệnh `kubectl expose`. Thường thì mọi người không nhớ format của deployment và chỉ xài `kubectl run` với flags `-o yaml` và `--dry-run` để lấy output ra và edit tiếp. Lệnh này được sử dụng cực kì phổ biến và sử dụng rất nhiều khi thi CKA (Certified Kubernetes Administrator) hay CKAD (Certified Kubernetes Application Developer). kubectl create deployment nginx --image=nginx:alpine -o yaml --dry-run Bởi vậy nếu ai có ý định thi CKA/CKAD thì cố gắng nhớ format của mấy loại resource cơ bản đi nhé :) --- --- date: "2020-05-01T00:00:00Z" description: A step by step guide on how to use Synology NFS as external storage with Kubernetes image: /img/synology-nfs.png tags: - Homelab - Kubernetes title: Using Synology NFS as external storage with Kubernetes thumbnail: "/img/k8s-storage.jpg" --- For home usage, I highly recommend `microk8s`. It can be installed easily with `snap`. I'm not sure what's the deal with `snap` for Ubuntu desktop users but I've only experience installing `microk8s` with it. And so far, it works well for the purpose. Initially, I went with Docker Swarm because it's so easy to setup but Docker Swarm feels like a hack. Also, it seems Swarm is already dead in the water. And since I've already been using Kubernetes at work for over 4 years, I finally settle down with `microk8s`. The other alternative is `k3s` didn't work quite as expected as well but this should be for another post. # Setup a simple Kubernetes cluster Setting Kubernetes is as simple as install `microk8s` on each host and another command to join them together. The process is very much simliar with Docker Swarm. Follow the guide on [installing](https://microk8s.io/docs/) and [multi-node setup](https://microk8s.io/docs/clustering) on microk8s official website and you should be good to go. Now, onto storage. I would like to have external storage so that it would be easy to backup my data. I already have my Synology setup and it comes with NFS so to keep my setup simple, I'm going to use Synology for that. I know it's not the most secure thing but for homelab, this would do. *Please note that most the tutorial for Kubernetes will be outdated quickly. In this setup, I will be using Kubernetes v1.18.* ## Step 0: Enable Synology NFS Enable NFS from `Control Panel` -> `File Services` ![](/img/synology-nfs.png) Enable access for every node in the cluster in `Shared Folder` -> `Edit` -> `NFS Permissions` settings. ![](/img/nfs-permission.png) There're few things to note here - Because every nodes need to be able to mount the share folder as `root` so you need to select `No mapping` in the `Squash` dropdown of `NFS Permissions`. - Check the `Allow connections from non-previleged ports` also. # With Helm `nfs-client` external storage is provided as a chart over at [kubernetes incubator](https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client). With Helm, installing is as easy as ```sh helm install stable/nfs-client-provisioner --set nfs.server= --set nfs.path=/example/path ``` # Without Helm ## Step 1: Setup NFS client You need to install `nfs-common` on every node. ``` sudo apt install nfs-common -y ``` ## Step 2: Deploy NFS provisioner Replace `SYNOLOGY_IP` with your Synology IP address and `VOLUME_PATH` with NFS mount point on your Synology. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: quay.io/external_storage/nfs-client-provisioner:latest volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: fuseim.pri/ifs - name: NFS_SERVER value: - name: NFS_PATH value: volumes: - name: nfs-client-root nfs: server: path: ``` Setup RBAC and storage class ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io ``` ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME' parameters: archiveOnDelete: "false" allowVolumeExpansion: "true" reclaimPolicy: "Delete" ``` ## Step 3: Set NFS as the new default storage class Set `nfs-storage` as the default storage class instead of the default `rook-ceph-block`. ```sh kubectl patch storageclass rook-ceph-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' ``` ## Testing We will create a simple pod and pvc to test. Create `test-pod.yaml` and `test-claim.yaml` that looks like this in a `test` folder ```yaml kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: gcr.io/google_containers/busybox:1.24 command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && exit 0 || exit 1" volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim ``` and `test-claim.yaml` ```yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim annotations: volume.beta.kubernetes.io/storage-class: "nfs-client" # nfs-client is default value of helm chart, change accordingly spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi ``` And do `kubectl create -f test/`. You should see the PVC bounded and pod completed after awhile. Browse the NFS share and if you see a folder is created with a `SUCCESS` file inside, everything is working as expected. ![](/img/nfs-test.png) --- --- date: "2020-04-25T00:00:00Z" tags: - Kubernetes title: 'Debugging Kubernetes: Unable to connect to the server: EOF' thumbnail: "/img/homelab-rack.jpg" --- We had an EC2 instance retirement notice email from AWS. It was our Kubernetes master node. I thought to myself: we can simply just terminate and launch a new instance. I've done it many times. It's no big deal. However, this time, when our infra engineer did that, we were greeted with this error when trying to access our cluster. ``` Unable to connect to the server: EOF ``` All the apps are still fine. Thanks to Kubernetes's design. We can have all the time we need to fix this. So `kubectl` is unable to connect to Kubernetes's API. It's a CNAME to API load balancer in Route53. That's where we look first. ## Route53 records are wrong So ok. There are many problems which can cause this error. One of the first thing I notice is the Route53 DNS record for `etcd` is not correct. It was the old master IP address. Could it be somehow the init script unable to update it? So our first attempt to fix it was manually update the DNS record for `etcd` to the new instance's IP address. Nope, the error is still the same. ## ELB marks master node as `OutOfService` We look a little bit more into the ELB for API server. The instance was masked `OutOfService`. I thought this is it. It makes sense. But what could cause the API server to be down this time? We've done this process many times before. We sshed into our master instance and issue `docker ps -a`. There is nothing. Zero container whatsoever. We check `systemctl` and there it is, the `cloud-final.service` failed. We check the logs with `journalctl -u cloud-final.service`. We noticed from the logs that many required packages were missing like `ebtables`, etc... when `nodeup` script ran. ## Manual `apt update` So if we can fix that issue, it should be ok right? We issue `apt update` manually and saw this ``` E: Release file for http://cloudfront.debian.net/debian/dists/jessie-backports/InRelease is expired (invalid since ...). Updates for this repository will not be applied. ``` Ok, this still makes sense. Our cluster is old and the release file is expire. If we manually update it, it should work again right? We do apt update with `valid until` flag set to `false`. ``` apt-get -o Acquire::Check-Valid-Until=false update ``` ## Restart cloud-final service Restart `cloud-final.service` or manually run the `nodeup` script again with ``` /var/cache/kubernetes-install/nodeup --conf=/var/cache/kubernetes-install/kube_env.yaml --v=8 ``` `docker ps -a` at this point should show all the containers are running again. Wait for awhile (30seconds) and `kubectl` should be able to communicate with the API server again. ## Final While your problem may not be exactly same as this, I thought I would just share my debugging experience in case it could help someone out there. In our case, the problem was fixed with just 2 commands but the actual debugging process takes more than an hour. --- --- date: "2020-04-01T00:00:00Z" description: Everything you should know before buying your first homelab rack. image: /img/patch-brush-panel.jpg tags: - Homelab title: Tips for first time rack buyer thumbnail: "/img/homelab-rack.jpg" --- Few weeks ago, I knew nothing about server rack. I frequent [/r/homelab](https://reddit.com/r/homelab) a lot in order to learn to build one for myself at home. These are the lessions I learnt during building my very first homelab rack. ## Choose the right size You need to care 2 things about a rack size: height & depth. The width is usually pretty standard 19 inches. - Rack height is meassured in U (1.75 inch or 44.45mm): a smallest height of a rack-mountable unit. - Rack depth is very important too. Usually available in 600/800 or 1000mm. Don't buy anything shallower than 800mm unless you plan to use the rack mostly for network devices. Otherwise, your rackmount server options are very limited. If you must go with 600mm depth rack, you can choose some half depth servers like ProLiant DL20, Dell R220ii, some Supermicro servers or build one yourself with a desktop rackmount cases. ![1u rack unit](/img/rack-1u.png) Carefully plan what kind of equiments you want to use to get the correct size. An usual rack usually have these devices: - 1 or more patch/brush panel for cable management (1U each) - 1 router (1U) - 1 or 2 switches. (1U each) - servers: this depends on how much computing power you need. Also servers come in various sizes (1U/2U/3U/4U) as well. - NAS maybe (1-2U) - PSU: usually put at the bottom (1U or 2U) - PDU: some people put it at the front, some puts it at the back. (1U) ## Things to looks for when selecting a rack - Rack type: open frame / enclosures or wall-mounted rack. - Wheel or not wheel, that is the question. I recommend you to go with wheel for home usage. - If you choose wheel, get a rack that has wheel blockers. - Does the rack's side panel can be taken off? If it does, it will make equipment installation a lot easier. ## Cable management ![brush panel & patch panel](/img/patch-brush-panel.jpg) The top U is patch panel. The third one is brush panel. The purpose of these panels is pretty easy to understand. I didn't know the term to search for at first when I want to buy one. Here are some accesories that helps with cable management: - Zip tie - Velcro - Cable combs - Patch panel - Brush panel - Multi-colored cables: eg green for switch to path link, orange for guest VLAN, etc... Some notes on the patch panel. There is punch down type that looks like [this](/img/punch-down-patch-panel.jpg) and there's pass-through type that looks like [this](/img/keystone-patch-panel.jpg). You probably want the keystone one as it's easier to maintain. If you cannot find cable combs, i saw people has been using zip tie to make DIY cable comb. It's pretty cool. ![diy cable comb using zip tie](/img/cable-comb-zip-tie.png) ## Other tips Numbering unit on the rack if it doesn't have one will help a lot when installing equipments. Like this ![label on rack](/img/manual-number-rack.png) Most racks I saw on [/r/homelab](https://reddit.com/r/homelab) have this but the cheap rack I got doesn't. I just got to be creative: use label maker tape along the rack's height and hand wrote the number there. Know something that isn't on this list, please tweet me at [@tuananh_org](https://twitter.com/tuananh_org). I would love to learn about your homelab hacks. --- --- date: "2020-04-01T00:00:00Z" image: /img/unifi-port-forward.png tags: - Homelab title: How to setup reverse proxy for homelab with Caddy server thumbnail: "/img/homelab-rack.jpg" --- The end goal is to be able to expose apps deployed locally on homelab publicly. I don't want to expose multiple ports to the Internet for several reasons: - I have to create multiple port-forwarding rules. - The address not memorable because I need to remember the ports. Eg: `homeip.example.com:32400` for Plex, `homeip.example.com:1194` for VPN, and so on... The alternative is to use reverse proxy. - setup reverse proxy - setup port forward (80 & 443) for reverse proxy - config reverse proxy to proxy the local apps ## Reverse proxy I would have gone with nginx but I want to tinker with [Caddy](https://caddyserver.com/). I have never used Caddy in production and this seems like a good excuse to learn about it (That's what homelab is for right?). Caddy comes with HTTPS by default via Lets Encrypt. It's perfect for home usage. I was wondering if it's possible to proxy upstream to Docker host. Turns out it's possible. You just have to use `host.docker.internal` as upstream address. ([ref](https://dev.to/kevbradwick/how-to-setup-a-reverse-proxy-to-your-host-machine-using-docker-mii)) ``` docker run -d -p 1880:80 -p 18443:443 --network home-net \ -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile \ -v $(pwd)/site:/usr/share/caddy \ -v $(pwd)/data:/data \ -v $(pwd)/config:/config \ caddy/caddy caddy run -config /etc/caddy/Caddyfile --watch ``` Notice that I run Caddy in `home-net` network there, so that I can easily proxy other containers. ## Dynamic DNS You need to setup - an A record for your home IP (eg: `homeip.example.com`) - multiple CNAME records for each of your apps (eg: `plex.example.com` CNAME to `homeip.example.com`). I covered this topic in [a previous post](/2020/03/27/dynamic-dns-with-cloudflare/) of mine here. ## Port forwarding You need to do port forwarding (80 & 443) for your Reverse proxy. The setting is different, largely depends on your lab equipment and your ISP. I was stuck for a day debugging why port forwarding didn't work and it turned out, my ISP use NAT public IP address. ![port forwarding](/img/unifi-port-forward.png) ## Verify To test this, I create an nginx container with ``` docker run --name nginx --network home-net -d nginx ``` And edit the `Caddyfile` to this ``` example.com { reverse_proxy / nginx:80 } ``` And it should show nginx default page ![](/img/nginx.png) Also, you should see the page in HTTPS. ![](/img/caddy-ssl.png) --- --- date: "2020-03-31T00:00:00Z" image: /img/vpn-server.png tags: - Homelab - VPN - NAS title: How to setup a home VPN with Synology NAS thumbnail: "/img/homelab-rack.jpg" --- Currently, I'm working on building my homelab. It's still a very much work in progress but everything is coming along nicely. ![homelab](/img/homelab-1.jpeg) I plan to host lots of stuff in my homelab and be able to access it while I'm not at home. I don't feel comfortable exposing them all to the Internet so VPN to the rescue. The setup is straight forward. It's different, depends on your lab equipment but the steps are always the same. 1. Setup VPN server in your homelab. 2. Setup port forwarding in your router. 3. [*Optional*] If your IP address is dynamic, you can [setup dynamic DNS](/2020/03/27/dynamic-dns-with-cloudflare/) so that we can access the VPN server by domain. First step is rather easy. I already have a Synology NAS and they have the built in VPN Server app ready to install from their package store. It's just 1-click away. You install it, enable OpenVPN protocol and it's done. Click export configuration afterward. The UniFi Security Gateway also have built-in VPN server but I figure since the NAS is more powerful, I think I should offload the work to the NAS. ![synology vpn server](/img/vpn-server.png) The second step can be done via your router. In my case, I use UniFi hardware so I'm gonna do it via UniFi Controller in `Settings` -> `Routing & Firewall` -> `Port forwarding`. ![unifi controller port forwarding](/img/unifi-port-forward.png) Optionally, if your IP address is dynamic, you may want to setup dynamic DNS (eg: myvpn.example.com). I already covered it in a [previous post using Docker and CloudFlare](/2020/03/27/dynamic-dns-with-cloudflare/). Now, edit the exported configuration and replace the server IP address with your static IP address or your dynamic DNS above. Try connect with OpenVPN client and if all is good, you should be connected. ![openvpn](/img/openvpn-status.png) ## Troubleshoot I found Ubiquiti has an [excellent troubleshoot guide](https://help.ubnt.com/hc/en-us/articles/235723207-UniFi-USG-UDM-Port-Forwarding-Configuration-and-Troubleshooting) available on their website. Some common problems are: - Double NAT (local): You have 2 routers on your local network. In that case, you either have to remove 1 router (change to AP mode?) or setup port-forwarding on both. - NAT public IP address: if you see your public IP address via your router and via, say Google, doesn't match. That's probably it. See the below picture, if the two IPs are not same, you got NAT public IP address. ![](/img/nat-public-ip.png) If you go through all that and it's still not working, it's probably has something to do with the ISP. --- --- date: "2020-03-30T00:00:00Z" tags: - Homelab - UniFi title: How to adopt UniFi Security Gateway to an existing network thumbnail: "/img/homelab-rack.jpg" --- I'm by no mean a network expert. This is just my personal experience when I setup my USG to my existing network. --- In my case, I was using Orbi RBK as my router and access point. With USG in place, I will use the Orbi in access point mode. The USG will be replacing the Orbi as my router. My current network is using `10.0.0.1/8` IP range. By default, USG uses `192.168.1.1` IP which means I won't be able to adopt it just by plugging it to my current network. So I need to change its IP address first. You're gonna need a PC/laptop with Ethernet port in order connect to the USG and change its IP address. Luckily, I have a desktop PC with me. So I installed the UniFi Controller and connect the USG to the PC's ethernet port. I set the ethernet IP of the desktop to something in the `192.168.1.1` range like `192.168.1.6` with subnetmask `255.255.255.0`. Once that's done, I open up UniFi Controller and we will be able to see and adopt the USG. The default username and password is `ubnt` by the way. After adopting the device, if you want to keep using the old IP and subnet, you will have to go to `Settings` -> `Networks` and edit the LAN network to use your desire IP and subnet. Now, in order to replace the old router, you will have to configure the PPPOE info as well. From `Settings` -> `Networks`, click edit the WAN network and enter your Internet username & password there. ![](/img/unifi-wan-settings.png) Now, I'm not sure where you're from, what do you need to do to replace the router but here in Vietnam, I will need to call the ISP and ask them to remove the MAC address cache of the router as well. After that, I just have to connect the internet cable to the Internet port of USG. Connect the LAN port of USG to Internet port of Orbi and it's done. Internet is back online. ![adopt usg to an existing network](/img/adopt-usg-done.png) --- --- date: "2020-03-27T00:00:00Z" tags: - Homelab - CloudFlare - DDNS title: Dynamic DNS with CloudFlare thumbnail: "/img/homelab-rack.jpg" --- I use this project `oznu/docker-cloudflare-dns`[^github-repo] where the author implements everything in bash, `curl` and `jq`. There were a bunch of projects that does this DDNS with CloudFlare but I chose this project because of this uniqueness. To use this, you just have to create an API token with Cloudflare that has these permissions: - Zone - Zone Settings - Read - Zone - Zone - Read - Zone - DNS - Edit Also, set zone resources to `All zones`. And then, run the docker container docker run -d \ -e API_KEY= \ -e ZONE= \ -e SUBDOMAIN= \ --restart=unless-stopped oznu/cloudflare-ddns ![ddns cloudflare docker](/img/ddns-docker-cloudflare.png) [^github-repo]: [https://github.com/oznu/docker-cloudflare-ddns](https://github.com/oznu/docker-cloudflare-ddns) --- --- date: "2020-03-02T00:00:00Z" tags: - Hacker News title: 'Traffic from #1 post on Hacker News' description: "My post happened to be featured #1 on HackerNews and the kind of traffic you should expect when it happens" url: /2020/03/02/traffic-from-top-post-on-hacker-news/ thumbnail: "/img/hn-top1.jpg" pinned: true --- My last post recently [featured #1 on Hacker News](https://news.ycombinator.com/item?id=22410448). This surprises me a little bit because when I [first posted the link](https://news.ycombinator.com/item?id=22380685), there were little interest (~15 points) in it. I shake it off and went on with my day. Few days later, when I was browsing Hacker News, my blog post was featured right there on the home page through someone else's submission. The post stays at #1 for ~ 12 hours before drift off to page 2. It also draws some interest on Twitter with the peak is [Jeff Barr wrote a tweet about it](https://twitter.com/jeffbarr/status/1232348629291040770). Here are some numbers for you stats junkie; just so you know what kind of traffic you can expect from HackerNews. The post attracts 15k unique users on the day of submission, 3k users on second day and eventually back to normal on the 4th day. ![](/img/hn-ga-0.png) Traffic stats on Cloudflare seems a bit inflated with the unique visitors at 36k. Maybe they do count bots as well there. ![](/img/hn-cloudflare-0.png) Since my site is static and hosted by Cloudflare Workers Site, the load speed is pretty much the same throughout the day. Big fan of Cloudflare and their products. --- --- date: "2020-02-20T00:00:00Z" tags: - Talk - Hacker News title: 'The story behind my talk: Cloud Cost Optimization at Scale: How we use Kubernetes and spot instances to reduce EC2 billing up to 80%' description: "The story behind the talk I gave at Vietnam Web Summit 2018" url: /2020/02/20/the-story-behind-my-talk-cloud-cost-optimization-at-scale/ thumbnail: "/img/cloud-cost.jpg" pinned: true --- [👉 Discussion on HackerNews](https://news.ycombinator.com/item?id=22410448) ![me at vietnam web summit 2018](/img/vws2018.jpg) This is the story behind [my talk](/talks/): "Cloud Cost Optimization at Scale: How we use Kubernetes and spot instances to reduce EC2 billing up to 80%". Now before I tell this story, I will admit first hand that the actual number is lower than 80%. ## 2015 The story began in mid 2015 when I was employed by one of my ex-employer. It was a .NET framework shop that struggled to scale in both performance and cost at the time. I was hired as developer to work on the API integration but I can't help to notice too much money was sunk into AWS EC2 billing. Bear in mind I'm not an Ops guy by any mean but you know about startup, one usually has to wear many hats. At first, when the AWS credit is still plenty, we don't have to worry much about it. But when it ran low, it's clearly becoming one of the biggest pain point of our startup. The situation at the time was like this: * There were 2 teams: the core team using .NET framework and the API team using Node.js * Core team mostly uses Windows-based instance and API team uses Linux-based. * The core team uses a lot more instances than API team. * Most EC2 instances are Windows-based. All are on-demand instances. No reserved instances whatsoever 😨. * Few are Linux-based instances where we install other linux based applications but there weren't many of them. * On-demand Windows-based instance price is about 30% higher than on-demand Linux instances. * We use RDS for database. * We don't have any real ops guy as you think these days. Whenever we need something setup, we just have to page someone from India team to create instances for us and then proceed to set them up ourselves. Now, the biggest cost are obviously RDS and EC2. If I were to assigned to optimize this, I will definitely take a look at those 2 first. But I wasn't working on it at that time. I was hired to do other things. At that time, I was using Deis - a container management solution (acquired by Microsoft later) for my projects. I experimented shortly with [Flynn](https://flynn.io/) but ended up not using it. ## 2016 ### Spotinst In 2016, I heard of this startup called [Spotinst](https://spotinst.com/). I found several useful posts from [their blog](https://spotinst.com/blog/) regarding EC2 cost optimization and find their whole startup ideas very fascinating. For those of you who are not working with infrastructure, the whole idea of Spotinst is to use spot instances to reduce the infrastructure cost for you. And they take some cut from it. > Spotinst automates cloud infrastructure to improve performance, reduce complexity and optimize costs. Spot instances are very cheap (think 70-90% cheaper vs on-demand) EC2 offering from AWS but comes with a small problem: it can goes away anytime with just [2 minutes notice](https://aws.amazon.com/about-aws/whats-new/2015/01/05/announcing-amazon-ec2-spot-instance-termination-notices/). I thought if we can design our workload to be fault tolerant and gracefully shutdown, spot instances will make perfect sense. Or anything like a queue and worker workload would fit as well. Web apps, on another hand, will be a little bit more difficult but totally do-able. ### Kubernetes During 2016, I also learnt about this super duper cool project called [Kubernetes](https://kubernetes.io/). I believe they were at version 1.2 at the time. Kubernetes comes with the promise of many awesome features but what caught my eyes were this "self-healing" feature. This make perfect complement with spot instances, I thought. And so I dig a little bit more to see if I can set one up with spot instances and [they do support it](https://github.com/kubernetes/kubernetes/commit/fb12c48cb7d060d351e97124929cd52bd9a62a74). Awesome!! 🥰 Now, the only problem left is our core team still need Windows and Kubernetes didn't support Windows at the time. So my whole infrastructure revamp idea is useless now, or so I thought. ### .NET Core In mid 2016, I learnt about .NET core project. They were around 1.0 release at the time. One of the feature is cross-platform. I thought to myself: I can still salvage this. Now, please note that I'm a Node.js guy and I don't know much about .NET aside from my thesis in university. So I asked the lead guy from core team to take a look into it and while there are many quirks, it's actually not very difficult to migrate our core to .NET Core. It would be time consuming but it's very much doable. I know that .NET Core is going to be the future so eventually, we will need to migrate to it anyway. ### Tests + Migration While the core team do that, I setup a test cluster with spot instances and learnt Kubernetes. I optimized the cluster setup a little bit and migrate all my projects over to them by the end of 2016. The whole process is quite fast because all the apps I have (Node.js) are already Dockerized and have graceful shutdown implemented. I just need to learn the in-and-out of Kubernetes. I started with a managed [GKE](https://cloud.google.com/kubernetes-engine) at first using their free $300 credit, to learn the basic of Kubernetes; but then later on use [kops](https://github.com/kubernetes/kops) to setup a production cluster with AWS. Some of the changes I did for the production cluster is: * Setup instance termination daemon to notify all the containers + graceful shutdown for all the apps. * Setup multiple instance groups of various size and availablity zone, mixing spot instances with reserved instances. This is to prevent price spike of certain spot instance group; and minimize the chances of all spot instances going down at the same time. * Calculate and provision a slightly bigger fleet then what we actually need so that when there were instances shut off, there won't be service degradation. Because spot instances are so cheap, we can do this without worry much about the cost. * Watch to see if there were scheduling failture to scale the reserved instance groups. ## 2017 At this point, our API apps' EC2 cost is already very managable. We're waiting for the core team to migrate over. And we did that in 2017. The overall cost saving for EC2 was around 60-70% because we need to mix reserved instances in and provision a little higher than what we actually need. We were very happy with the result. What we did back then is actually what Spotinst does but at much smaller scale. And it's more doable with smaller startups with only 1 ops guy. And that is my story behind the talk: "Cloud Cost Optimization at Scale: How we use Kubernetes and spot instances to reduce EC2 billing up to 80%". *Update: [#1 on HackerNews](https://news.ycombinator.com/item?id=22410448). Yay!* --- --- date: "2020-02-13T00:00:00Z" tags: - CloudFlare title: Thoughts on Workers KV thumbnail: "/img/cloudflare-worker.jpg" --- ## Infrequent write / frequent read I tried to build a [todobackend.com](https://todobackend.com/) with Cloudflare workers and Workers KV. However, the specs runner keeps failing, inconsistently. Meaning they would pass this run and fail the next. Manual tests usually doesn't have this problem. This tells me it seems Workers KV is not synchronous or the data replication is slow. Turns out, it's mentioned right there in the Workers KV's docs; emphasises are mine. > Workers KV is generally good for use-cases where you need to **write relatively infrequently**, but read quickly and frequently. It is optimized for these high-read applications, only reaching its full performance when data is being frequently read. Very infrequently read values are stored centrally, while more popular values are maintained in all of our data centers around the world. > KV achieves this performance by being **eventually-consistent**. New key-value pairs are immediately available everywhere, but value **changes may take up to 60 seconds to propagate**. Workers KV isn’t ideal for situations where you need support for atomic operations or where values must be read and written in a single transaction. With this, todobackend.com specs runner would never pass. The Workers KV API is quite simple for now and I do hope they keep it that way, or maybe resemble Redis API with some more data types like list / sorted set. That would be lovely. ## Batch load is not yet supported It's on roadmap but not yet available. So the result of `.list()` will have to be mapped with a `Promise.all` like this await Promise.all(keys.map(key => myKvStore.get(key.name))) With these limitations, Workers KV are more suitable for keeping build assets or anything that doesn't need close-to-real-time propagation. Keep that in mind when you want to build something with Workers KV; also watch this space as Cloudflare is moving pretty fast. Some other limits can be found [here on Cloudflare workers docs](https://developers.cloudflare.com/workers/about/limits/#body-inner). --- --- date: "2020-02-09T00:00:00Z" link: https://github.com/tuananh/reader tags: - CloudFlare title: reader description: "My experiment with CloudFlare workers to build a website that generate clean, readability-like view that you can share on the web" thumbnail: "/img/cloudflare-worker.jpg" --- Another experiment with Cloudflare workers. I haven't use Worker KV here though. [reader](https://github.com/tuananh/reader) is a service that mimic reader mode on browser and let user shares the reader mode view on the web. It's still super buggy now due to lib that I use is quite abandoned at the moment. I just want to whip out something that works first. ![reader](/img/reader.png) Something I learnt from reading Cloudflare workers docs while doing this: * HTMLRewriter is delightful even though I didn't get to use it (much) in this small project. * Worker KV is another nice bit from them. With this, it's probably be enough to build a complete web apps. Next, I'm going to look at Worker KV and HTMLRewriter more in an attempt to build something that use both of those features. --- --- date: "2020-01-29T00:00:00Z" link: https://tuananh.net tags: - CloudFlare title: Experiment with Cloudflare Workers description: "Experiment with CloudFlare workers to host this website" thumbnail: "/img/cloudflare-worker.jpg" --- I've been meaning to try Cloudflare Workers with my blog. Given that it's static website, it should be straight forward to do. They (Cloudflare) makes it incredibly easy to migrate. [Their tutorial](https://developers.cloudflare.com/workers/sites/start-from-existing/) works just fine with a minor exception regarding the DNS setup. The whole process took like 5 mins overall. I just had to do an additional step of setting up an A record of my domain to `192.0.2.1` so that they can be resolved to Cloudflare Workers. The site is now up at [https://tuananh.net](https://tuananh.net). Eventually, if all is well, I think i'm gonna migrate mine currently on RamNode over to them. --- --- date: "2020-01-02T00:00:00Z" link: https://jvns.ca/blog/brag-documents/ title: Brag document thumbnail: "/img/brag-document.jpg" --- > There’s this idea that, if you do great work at your job, people will (or should!) automatically recognize that work and reward you for it with promotions / increased pay. In practice, it’s often more complicated than that – some kinds of important work are more visible/memorable than others. It’s frustrating to have done something really important and later realize that you didn’t get rewarded for it just because the people making the decision didn’t understand or remember what you did. So I want to talk about a tactic that I and lots of people I work with have used! I've been doing this for years and it really works. Highly recommend you give [this post](https://jvns.ca/blog/brag-documents/) a read. --- --- date: "2019-12-18T00:00:00Z" tags: - Tip - Git title: Debugging with git bisect description: "A little tip on how you can debug with git bisect command" thumbnail: "/img/git-bisect.jpg" --- Suppose I have this project with 5 commits. You can clone it from [here](https://github.com/tuananh/git-bisect-demo/commits/master). ![git bisect](/img/git-bisect.png) Say, there's a regression bug in the master branch but a lot has been added to `master` after the feature was first inroduced. How would I go debugging this? Which commits break it? Usually, we would go manually and see which commit would possibly do this but if the project is large and active, it's a quite troulesome process. Luckily, we have `git bisect` for that. - Go to the project and issue `git bisect start` - Mark the bad commit by `git bisect bad `. You can ommit the commit id if you're already on it. - Mark the good commit by `git bisect good `. - Add the tests for the regression bug. In this case it's `add()` function. I'm gonna go ahead and add a fail test case for the regression bug I'm having. You may argue why not add test in the first place? Well, this is just an example so I have 0 test for it. In actual scenario, you can have an extensive test cases but still can miss an edge case. In that scenario, you will need to add that fail test for that edge case here. ```js // test.js const assert = require('assert') const add = require('./add') assert(add(1,2) === 3, 'one plus two should equal to three') ``` - Do `git bisect run `. In this case, it would be `git bisect run node test.js`. - Do `git bisect log` and see the result. It would look like this. ```sh # bad: [addb180af061bbfbad298cd6a9ad2110df0f873e] feat: add multiply git bisect bad addb180af061bbfbad298cd6a9ad2110df0f873e # good: [7688391b1a9b133bef92198e376c9f5979260ade] feat: add add() function git bisect good 7688391b1a9b133bef92198e376c9f5979260ade # bad: [d504f94f1d71c93deb9d9bbdf87bfe333bbecff6] chore: add readme git bisect bad d504f94f1d71c93deb9d9bbdf87bfe333bbecff6 # bad: [d516aaf29331953382a8558f013b683427d7a390] feat: add subtract() function git bisect bad d516aaf29331953382a8558f013b683427d7a390 # first bad commit: [d516aaf29331953382a8558f013b683427d7a390] feat: add subtract() function ``` There you can see which first commit makes the test fail is `[d516aaf29331953382a8558f013b683427d7a390] feat: add subtract() function`. - Do `git bisect reset` when you're done. Happy coding! --- --- date: "2019-12-01T00:00:00Z" tags: - Tiling window manager title: The state of tiling window manager on Windows 10 thumbnail: "/img/windows-10.jpg" --- ## The state ### 1. [Workspacer](https://github.com/rickbutton/workspacer) * Opensource. * Best in term of feature set. * Closest thing to an actual tiling window manager on Windows 10. * Have some weird bugs that's quite annoying. * Development velocity is slow. ### 2. [Bug.n](https://github.com/fuhsjr00/bug.n) * Opensource. * Written in scripting language - AutoHotkey. Require AutoHotkey installed. ### 3. [PowerToys](https://github.com/microsoft/PowerToys) * Opensourced by Microsoft. * Not really tiling. It's still manual process. * Very stable for daily use. * Not really focusing on window management, it's just one of the features. * Development velocity is very fast. ## Conclusion I settled on PowerToys for now but keeping an eye on workspacer. I love workspacer's feature set. It actually has everything I need but lacking stability. --- --- date: "2019-10-27T00:00:00Z" title: A beginner guide to CPU air cooling description: "I was researching to build a new PC and was looking for what's the best CPU air cooler" thumbnail: "/img/cpu-air-cooling.jpg" --- I was researching about CPU air coolers for the best CPU air cooler and I learnt a lot of stuff. Thought this might be useful to some of you. Most of the stuff below are copy and pasted from various sources. This serves as a basic guide for absolute beginners like me when it comes to CPU air cooling. TL;DR: Noctua NH-D15 is the best air CPU cooler out there. ## Reading fan specs ### CFM, m³/h, RPM, dB/A and mm H20 Every fan features a cubic feet per minute (CFM) rating, which measures of the volume of air it moves in a minute. The greater the CFM, the more air a fan moves. To properly air cool your computer, you need have enough case fans to push or pull air into and out of the case. More case fans means higher total CFM and more air being moved through your computer. Some manufacturers use `m³/h` as unit. One m³/h is ~0.589 CFM and one CFM is ~1.699 m³/h. You can use website like [ConvertUnits.com](https://www.convertunits.com/from/cubic+m/hr/to/cfm) to convert them. ![](/img/fan-diagram.png) *The airflow is always moving from the front to the back of the fan* Other specs like `RPM` (rounds per minute) and `dB/A` which is the noise level are kind of self-explained. Higher RPM is cooler but also noisier. Lower `dB/A` is better. You may also notice `mm H2O` which stands for millimeters of water - which is measurement of "static pressure" - the amount of negative pressure it takes to makee a fan come to a complete stop at `xxx RPM`. As you can see, it's highly dependent on RPM speed. ### Fan connector pins A three pin connector is basically power (5/12 volt), ground, and signal. The signal wire measures how fast the fan is moving without any controls for the fan’s speed. With this type, fan speed is typically controlled by increasing or decreasing the voltage over the power wire. A four pin connector is a little different than the three pin connector since it has the extra (fourth) wire used for controlling and sending signals to the fan, which likely has a chip on it that tells it to slow down or speed up (in addition to the other wires the three pin connector has). You probably don't need to know more than that. ## Positive / Neutral / Negative air pressure [Explained by Linus Tech Tips](https://www.youtube.com/watch?v=g8EN3K-eaVA) Or you can just take a look at thousand words below: ### Positive pressure looks like this ![](/img/positive-pressure-diagram.png) ### Neutral pressure looks like this ![](/img/neutral-pressure-diagram.png) ### Negative pressure looks like this ![](/img/negative-pressure-diagram.png) ## Selecting a CPU air cooler Once you found an air cooler that you like, make sure it can be fit inside your case. Read your case specs and find `CPU cooler clearance`. To be sure, you can also go to site like [PCPartPicker](https://pcpartpicker.com/) and find complete builds features the case and cooler you intend to use. ![](/img/case-cpu-cooler-clearance.png) ## References: * [https://noctua.at/en/products/fan](https://noctua.at/en/products/fan) * [https://forum.corsair.com/v3/showthread.php?t=189016](https://forum.corsair.com/v3/showthread.php?t=189016) * [https://www.howtogeek.com/273575/what-is-the-difference-between-three-and-four-pin-cpu-fans/](https://www.howtogeek.com/273575/what-is-the-difference-between-three-and-four-pin-cpu-fans/) * [https://www.youtube.com/watch?v=g8EN3K-eaVA](https://www.youtube.com/watch?v=g8EN3K-eaVA) --- --- date: "2019-10-07T00:00:00Z" tags: - Redis title: Advanced filtering and sorting with redis (part 2) --- With the recent introduction of Redis modules (since Redis v4), redis is now a lot more flexible than the old redis. [Previously in part 1](/2018/06/12/advanced-filtering-and-sorting-with-redis-part-1/), if you want to mimic the sorting and filtering behavior, you need to use set/sorted set and do the intersection/union by yourself. Not anymore. ## Meet RediSQL RediSQL is an in-memory SQL engine, built on top of Redis as Redis module. ![](https://redisql.com/img/example-simple.png) It's pretty much SQL under the hood now. No more smart trick to mimic the behavior. The downside is there ain't many redis client that support browsing data for these modules, aside from the newly released [RedisInsight](https://redislabs.com/redisinsight/), which currently only support RedisGraph, RediSearch and RedisTimeSeries. This makes debugging is really troublesome. This is a big show stopper for me. Just something for you to keep in mind. ## RedisGraph RedisGraph is a graph database module for Redis. It's specificly built for graph database but can be utilized for doing filtering as well, because it's a graph database. It's kinda using the wrong tool for the purpose. RedisGraph is a lot more powerful than just doing filtering and sorting. Example of doing filtering in RedisGraph Loading data ``` GRAPH.QUERY TestGraph "CREATE (:Property {id: '1', name: 'hotel 1'})-[:hasFacility]->(:Facility {id: '1', name: 'Swimming pool'})" GRAPH.QUERY TestGraph "CREATE (:Property {id: '1', name: 'hotel 1'})-[:inCity]->(:City {id: '1', name: 'Hanoi'})" GRAPH.QUERY TestGraph "CREATE (:Property {id: '1', name: 'hotel 1'})-[:hasStarRating]->(:Rating {id: '4', name: '4 star'})" GRAPH.QUERY TestGraph "CREATE (:Property {id: '2', name: 'hotel 2'})-[:hasFacility]->(:Facility {id: '2', name: 'Spa'})" GRAPH.QUERY TestGraph "CREATE (:Property {id: '2', name: 'hotel 2'})-[:inCity]->(:City {id: '1', name: 'Hanoi'})" GRAPH.QUERY TestGraph "CREATE (:Property {id: '2', name: 'hotel 2'})-[:hasStarRating]->(:Rating {id: '3', name: '3 star'})" ``` Filter all 3 star hotels in Hanoi ``` GRAPH.QUERY TestGraph "MATCH (h:Property)-[:inCity]->(c:City) WHERE c.name = 'Hanoi' and r.name='3 star' RETURN h.id, h.name" ``` --- --- date: "2019-09-25T00:00:00Z" tags: - Homelab - Pihole title: Pi-hole thumbnail: "/img/pihole.jpg" --- I've heard a lot of praise about [Pi-hole](https://pi-hole.net/) project but hadn't gotten around actually trying it yet until recently. Pi-hole is a network-wide ad-blocking solution via local DNS. You set it up as a local DNS server and it will block all the ads that match the rule from DNS level. This way, you don't have to setup adblock on each and every devices you have, especially tablets and mobiles. See example of VnExpress without adblock below. ![vnexpress without adblock](/img/vnexpress-without-adblock.png) People usually use Pi-hole with a low powered device like Raspberry Pi (hence the name Pi-hole) but in my case, I already have an Intel NUC lying around as Plex server (running Windows 10). I could just use it instead of setting up sth new. The easiest way to install Pi-hole is to use Docker. The process is as easy as - Install Docker for Desktop. - Create a few folders for pihole config. Let's do it in Documents folder and mount it to container. If you change it to something else, make sure to update the following commands. Create the 3 folders with the folowing structure. ``` pi-hole-config/ ├── dnsmasq.d/ ├── pihole/ ``` - Download and run pihole Docker container with the following command ```sh docker run -d --name pihole \ -p 53:53/tcp \ -p 53:53/udp \ -p 80:80 \ -p 443:443 \ -v "/c/Users//Documents/pi-hole-config/pihole/:/etc/pihole/" \ -v "/c/Users//Documents/pi-hole-config/dnsmasq.d/:/etc/dnsmasq.d/" \ -e ServerIP="" \ --dns=127.0.0.1 \ --dns=1.1.1.1 \ -e WEBPASSWORD= \ --restart=unless-stopped pihole/pihole:latest ``` - I need to disable Windows Firewall for local network which I think it's safe to do at home in order to access the container from other machine. And that's it. Now you can head over to `HOST_IP/admin` to login and configure Pi-hole. Once it's done, you can config your router to use the `HOST_IP` as the default DNS server, and maybe Cloudflare or Google's DNS as backup. Ah, and make sure to set static IP for your Pi-hole host machine so you don't have to update router's setting if the IP changes. ![pihole admin](/img/pihole-admin.png) *Keep in mind that Pihole is not a complete replacement for browser extension like uBlock origin. You probably still needs that because DNS-based adblocking functionality is quite limited (eg: sites that serve ads on the same domain as content). It's mostly useful for mobile browser where adblocking is almost non-existent or not good enough.* --- --- date: "2019-09-01T00:00:00Z" tags: - Keyboard - Review title: Microsoft Sculpt Ergonomic Desktop review thumbnail: "/img/keyboard-review.jpg" --- While i'm a hardcore mechanical keyboard fan, this is my very fisrt split (partially), ergonomic keyboard. The keyboard comes in tenkeyless size and a separated numpad. It's a bit bigger for my taste but I can live with that. I'm more accustomed to a smaller board. 60%-65% is usually the sweet spot for me. The sculpt keyboard uses wireless but probably not Bluetooth which makes me wonder if I can find the replacement USB receiver dongle if I ever lost it. ## The keyboard Decent looks. Not like Apple-good looking but quite good. It's also look nicer in picture than in real life so keep that in mind Pretty standard layout with non-standard key size, with several keys of unusual size. This kind of split keyboard will help you realize if you're touch typing wrong. For example, i sometimes type Y with the left index finger. While its possible to do that on a normal keyboard, it's not do-able on split keyboard in general because it's now too far from the left index finger. I think of this as a good thing. I also suffer a short period of inaccuracy with the B key, although the size is same with other keys, it feels like the hit box is much smaller. Same thing with the Enter key. I'm also not a big fan of the ESC key and the whole Fn row. They're small and quite wobble. Coming from a big fan of mechanical keyboard, scissor switch is kind of meh. It's better than a typical laptop keyboard but so much worse than a mechanical keyboard. The typing feeling can only be as good as scissor goes. Media keys are nice. They work out of the box with apps like Spotify. There's a button at the top right corner to switch back and forth between Fn function keys and media keys. The biggest downside of this keyboard is that remapping keys (via firmware) is not possible. One of the thing I would like to do is to map the left spacebar to backspace. It's possible with the [Sculpt Comfort](https://support.microsoft.com/en-au/help/2757670/new-features-of-the-sculpt-comfort-keyboard) but not possible with Sculpt Ergonomic. You can only blame Microsoft for this. The issue has been there for a few years so I dont think they have any plan to fix this. From the [documentation on Microsoft website](https://support.microsoft.com/en-us/help/4052277/accessories-how-do-i-reassign-hot-keys-for-my-keyboard ), it seems remapping is a supported feature but it's super misleading. There are only 9 keys that can be customized and the available mapping options are quite limited. ![](/img/sculpt-ergo-remap.png) You may think the Caplocks can be customized from the above picture? Wrong. You can only turn it on or off and that's it. ![](/img/keyboard-mouse-center-remapping.png) Good thing is you can still do it via software. There are apps like [AutoHotkey](https://www.autohotkey.com/) where you can program it like how you like. But you will have to set it up again if you use the keyboard on another machine. Personally, I find [TouchCursor](http://martin-stone.github.io/touchcursor/) good enough for my needs. The software mostly focuses on SpaceFn layout but you can config it to use any key as the Fn key. ![](/img/touch-cursor.png) Also, you don't need Microsoft Mouse and Keyboard Center software. It's garbage. ## The mouse Let's talk about the mouse. It's goofy and make you feel kind of strange to hold it at first. It feels like you're holding a ball. Overall, it's a lot more comfortable than the coventional mouse with office work but I wouldn't use it for gaming though. The mouse back button requires a bit of force which I think they can improve in newer version. Also, lowering the button a little bit would be nice. ## Conclusion Even though I own lots of mechanical keyboards but I don't use them as often as this keyboard. This thing is slowly becoming my personal favorite. I would def. recommend if you don't need the some serious remapping feature (Check TouchCursor out to see if it's working well enough for you) or play games a lot. --- --- date: "2019-08-26T00:00:00Z" title: Optimal team size thumbnail: "/img/team-size.jpg" --- Recently, I saw [this tweet on Twitter](https://twitter.com/RichRogersIoT/status/1159872097205805056). This is regarding the challenge of adding more engineers to a project in relation to the number of communication lines. This is easy to notice adding 1 more engineer to the team will increase no. of communication lines quite a bit because the function to calculate no. of communications lines in regard to no. of engineers is O(n*n), no wonder why it's steep. More on that later. ## Amdahl's law This tweet immediately reminds me of [Amdahl's law](https://en.wikipedia.org/wiki/Amdahl%27s_law). Let's talk broader, instead of discussing about number of communication lines, how about we try to optimize the team throughput in relation to the number of engineers on the team. The most common misunderstanding in project management is how the project time is inversely proportional to the number of the team as you often hear this joke > A project manager is someone who thinks that 9 pregnant woman can create a baby in 1 month. This assumes everything on the project can be done parallel. Everyone's work is independent. This assumption is almost never true in real world project management. So, how can we refine the above function to better reflect the real world situation. The first thing that pops in my mind is the Amdahl's law. > Amdahl's law is often used in parallel computing to predict the theoretical speedup when using multiple processors. For example, if a program needs 20 hours using a single processor core, and a particular part of the program which takes one hour to execute cannot be parallelized, while the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many processors are devoted to a parallelized execution of this program, the minimum execution time cannot be less than that critical one hour. Hence, the theoretical speedup is limited to at most 20 times: Sounds like we can use this to further refine our function above. But first, this is Amdahl's law: Let's call `p` is propotion of work that can be parallel with `p=0` means nothing can be done parallelly and `p=1` means everything can be done paralelly. When `p=0` (nothing can be done in parallel), we got the `time = work`. When `p=1`, `time = work / n` as before ## Communication overhead Now, let's try to add communication overhead to the formula. Assuming commication cost is proportional to no. of communication lines with each of them takes a fixed cost in time, says k (assuming k is a small positive number). Of course, in reality, communication doesn't look like that. Maybe more like org chart. Or it could be group meeting to exchange information instead of pair-wise. This is just to say, when you want to optimize your team's throughput, you may want to check for communication method. It's certainly a big factor. But for the time being, let's just go with this. Our function will now become ## Conclusion So we can see that at first, adding engineers to a project will speed it up but if go beyond a certain optimal size, adding more engineers to a project will just make it slower. I don't think the above function can reflect the project estimation well enough. But I know for sure, up to a certain number, adding more people will slow the project down. Maybe it's more related to [Parkinson's law](https://en.wikipedia.org/wiki/Parkinson%27s_law) because "work expands so as to fill the time available for its completion". The question remains: what's the optimal team size? Do you think it will make a big difference? Yes or no, lemme know. --- --- date: "2019-08-07T00:00:00Z" title: From macOS to Windows 10 thumbnail: "/img/windows-10.jpg" --- I'm an Apple fan no doubt but recently, I've been using Windows 10 exclusively at work and to be honest, I've grown to quite fond of Windows 10. For the first time since 2008, I'm seriously thinking about buying a Windows laptop. With the recent introduction of Ryzen 3000 series, I was so tempted to build a PC again for the first time since 2006 (my last build was 13 years ago!!!) and install Windows 10 on it. Everything has been amazing experience since the switch. I don't have any regret at all. ## Lots of cool productivity apps ### [Everything (voidtools)](https://www.voidtools.com/downloads/) - FREE Amazing Spotlight replacement. I don't even understand how can it be that fast. Just pure magic! ### [Ditto](https://ditto-cp.sourceforge.io/) - FREE For clipboard management, Windows 10 has built-in feature as well but it's quite limited in term of feature set. Ditto is a free replacement that does the job very well. You can set max items in history, how long you want Ditto to keep the history. Type of things you want to keep in clipboard. Changing global hotkey, etc... Accessing clipboard history is just one shortcut away (I use Ctrl+Shift+V). ![ditto windows](/img/ditto.png) ### [ShareX](https://getsharex.com/) - FREE Very good screenshot annotation app. I use [Monosnap](https://monosnap.com/) on macOS but ShareX is a hell lot better. I prefer Monosnap UI a bit more but ShareX trumps Monosnap in term of features. Monosnap's upload destination is quite limited and the app is not free. With ShareX, I can simply press Ctrl+Shift+3, draw the screenshot rectangle, add some annotations and click upload (Imgur). Just like what I'm used to on macOS. ### [Microsoft Terminal](https://github.com/microsoft/terminal) Open-source terminal application from Microsoft. It's already quite good in preview build but what's more is the app development velocity is insane. You can see for yourself on GitHub. ## Very nice development experience macOS is a Unix-like operating system, which make it very developer-friendly. You get the nice GUI apps from macOS and you get the best of all the CLI tools from Linux. Now, all that stuff do-able on Windows 10 as well with the introduction of Windows SubSystem Linux (WSL). With WSL, developers get the best of all worlds. * You can use all the latest drivers for your hardware (Because Windows support is still first class) * You get to use Microsoft Office - which still is the best office suite out there. * You get to develop just like you're on an Unix machine (VSCode integration with WSL is also very good) * You can play games without rebooting into Windows. * etc... ![microsoft terminal preview](/img/wsl.png) ## Conclusion I still use macOS occasionally but I'm ok with the idea of moving to Windows entirely. I've yet to find anything that I miss from macOS. --- --- date: "2019-07-01T00:00:00Z" tags: - Tip - WebAssembly title: Tips on reducing WASM file size with Emscripten --- ## Optimize for size over performance If size is more important than performance, you can use `-Os` flag. I tried with camaro and the file size reduce from 176KB down to 130KB. It's worth a try. ## Disable assertion, debug Try adding these flags: `-s ASSERTIONS=0` and `-DNDEBUG` to emcc. ## Using emmaloc Try using `emmalloc` which is a smaller malloc version available in emcc by adding `-s 'MALLOC="emmalloc"'` flag ## Closure Using `--closure 1` flag may also help as well. ## Remove unnecessary dependencies Try not to import unnecessary libs in C++. Eg: don't forget to remove `iostream` if you only use it for debugging with `std::cout` Emscripten has a whole section about this, do read this to stay up to date. https://emscripten.org/docs/optimizing/Optimizing-Code.html#code-size --- --- date: "2019-05-27T00:00:00Z" tags: - Homelab title: Choosing a wireless router in 2019 thumbnail: "/img/wireless-router.jpg" --- I found [this guide](https://www.smallnetbuilder.com/basics/wireless-basics/33177-how-to-buy-a-wireless-router-2018-edition) to be extremely helpful. It was written in 2018 but the basic concepts are still true. Do read it if you want to learn what factors to consider when buying a wireless router in 2019. Tldr; I ended up buying [Netgear Orbi RBK40](https://www.netgear.com/orbi/rbk40.aspx). Or money is not a constraint, then [Netgear Orbi RBK50](https://www.netgear.com/orbi/rbk50.aspx). Here're why: - 802.11AC is a must. AX is not available yet. - 3 and 4 streams are still expensive for home usage. Orbi RBK40 has 2 streams and tri-band. Tri-band doesn't increase coverage or speed but it does help make a better backhaul (eg: handling more devices + Wi-Fi system performance in general) - Multipoint is better in my case (house with lots of wall) - **Dedicated** backhaul (2X2 (866Mbps)). This is a major factor for Wi-Fi performance. - [Ethernet backhaul](https://kb.netgear.com/000051205/What-is-Ethernet-backhaul-and-how-do-I-set-it-up-on-my-Orbi-WiFi-System) is possible. - Lots of Ethernet ports. USB port also. - 512 MB RAM 😱 - Mesh because it's extendable. I can just buy more satellite if I move to a bigger house. - Phenomenon performance in reviews. --- --- date: "2019-05-22T00:00:00Z" tags: - Tip - WebAssembly title: Some lessons learnt after converting a native module to WebAssembly --- I released my first WebAssembly module [here on GitHub](https://github.com/tuananh/camaro). Here are some lessons I learnt during the process. Please don't take it for granted. These things might be true, or not. I'm not sure. I just worked on it for the last few days. ## Cache WebAssembly instance to save some time on initialization const Module = require('yourmodule') const mod = Module() const resolveCache = new Map() mod.onRuntimeInitialized = function() { resolveCache.set(resolvePath, mod) // mod.your_method() } ## Size of WebAssembly files matter I accidentally publish an alpha build with `iostream` imported (I was debugging in C++ with `std::cout` !!!!), which result in `{.js,.wasm}` files significantly bigger. Bigger files mean longer time to download and compile. A small fix to remove `#include ` increases performance of camaro (wasm) from ~200 ops/second to ~260 ops/second. Still far from the the old camaro performance (~800 ops/second) but it's a start. To me, WebAssembly is a huge win. The performance hit is still big but I think it's just me doing something wrong haha. It's my first WebAssembly module so there's a lot to learn from here. Aside from that, camaro (wasm) is a big improvement in this version such as: - Smaller footprint, zero dependencies. - Closing a bunch of bug reports related to module installation on GitHub. - Serverless (eg: AWS Lambda) friendly as compilation is no longer required. No more downloading prebuilt artifacts from GitHub as well. ## I still don't know how to return arbitrary shape object from C++ to JS land I still don't know how to do this. For native module, it's easier with `node-addon-api`. I'm still trying to figure this out. This make `camaro` slows down a bit because I stringify the JSON object in C++ and parse again in JavaScript (😱 horrifying, I know!) ## References: - [Example of passing function options in C++](https://developers.google.com/web/updates/2018/08/embind#objects) --- --- date: "2019-05-09T00:00:00Z" tags: - Redis title: How to delete Redis keys using pattern --- So Googling this question show a common approach using `KEYS` command and then pipe them to `DEL` command, which looks something like this in Lua script local keys = redis.call('keys', ARGV[1]) for i=1,#keys,5000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end return keys Doing things like this would be fine for development where the keyspace is small and frequent of use is low. However, if you were to do this at a larger keyspace and a lot more frequent, the `KEYS` command will block Redis instance and making other commands to fail. The alternative is to use `SCAN` and loop until the cursor is 0. A naive implementation is like below. redis.replicate_commands() local matches = redis.call('scan', '0', 'match', ARGV[1], 'count', '10000') while matches[1] ~= "0" do for i=1,#matches[2],5000 do redis.call('del', unpack(matches[2], i, math.min(i+4999, #matches[2]))) end matches = redis.call('scan', matches[1]) end return matches[2] --- --- date: "2019-02-07T00:00:00Z" tags: - Redis title: Autocomplete at speed of light --- *tldr: [RediSearch](https://oss.redislabs.com/redisearch/index.html) - a full text search redis module that is super fast.* I [learnt of RediSearch 2 years ago](/2017/05/22/full-text-search-with-redis/). It was a young project back then but seems very promising at time. When I revisit it last year, it's quite mature already and was about to hit v1.0 so I did another test drive. The result was so good I put it on production few weeks later. Its pros are: - Fast (well!, it's redis). - No need to introduce another tech to our tech stack. You probably have Redis in your tech stack already anyway. - API are very well documented. (I had a junior developer working on this feature for merely 1 week) - If you're using a redis client like [ioredis](https://github.com/luin/ioredis), you don't need to add any additional dependency. ioredis already supports it via `redis.call()`. ## Fast 🚀 On my local machine, I can easily pull 1,500 requests per second (90k RPM). I guess it could be higher since I was running RediSearch in a Docker container. I was too lazy to set it up on my host machine. Most of the query returns in sub-60ms. You can see for yourselves on [MyTour.vn](https://mytour.vn) ![redisearch](/img/redisearch.png) ## Installing RediSearch The official Docker images are available on Docker Hub as `redislabs/redisearch`. We're using Docker and Kubernetes at work so it comes in very handy. For example, the command below spawns a RediSearch docker container for you to try locally ``` docker run -d -p 7000:6379 redislabs/redisearch:latest redis-server --loadmodule /usr/lib/redis/modules/redisearch.so ``` ## Using RediSearch Using RediSearch is quite simple. You just have to create indexes and preload your data to RediSearch. You can also specify the data type(TEXT, NUMERIC) and its weight for each of the index field. Once the data is loaded, you can call `FT.SEARCH` to do the autocomplete. We find BM25 algorithm works best for our use case instead of the default TFIDF. In our use case, the whole thing is written in ~ 200 lines of code, cache population included. ## Known issue - RediSearch doesn't work on case sensitive unicode characters; see [issue #291](https://github.com/RedisLabsModules/RediSearch/issues/291). However, there's workaround for that. You can either normalize the query or you can keep the origin data in a separate field. --- --- date: "2018-12-26T00:00:00Z" title: '2018: year in review' description: "My 2018 year in review" --- TLDR: ✨ 2018 in review ✨ 👩🏻‍💼 My wife got promoted 👨‍💻 Another amazing year at work for me 🏠 We bought a house 🤑 We paid off the loan early 🎤 Gave 2 public talks (or 1. The first one is a rather small audience - 50-ish people) --- ## We bought a house My wife and I went for a major decision earlier this year. We decided to buy a house with a little loan. We both agreed to live below our means and push ourselves really hard to pay it off within a year. We did it in 8 months 🔥 The fact that my wife got promoted also did help speeding it up. ## Push for Kubernetes adoption at work I pushed for Kubernetes adoption with my current company and it finally went live later in July. We switch from the classic on-demand instances over to spot instances mixed with reserved instances and Kubernetes for wordload orchestration. EC2 cost reduces more than 50 percent as the result. ## Talks I did [two talks this year](https://tuananh.org/talks/) on cost saving optimization with Kubernetes. Actually, I wrote one keynote talk and gave it twice. - Kubernetes Meetup #2 in March - Vietnam Web Summit 2018 in December The solution is basically Spotinst but available for small and medium size business without 30% cut from Spotinst. Speaking plan for 2019: maybe 3 or 4 talks. Maybe "Cost saving optimization with Kubernetes and spot instances" for the last time and something new. --- 2018, you were good to us ❤️ Here is to an amazing year of 2019! --- --- date: "2018-12-01T00:00:00Z" tags: - Keyboard title: My keyboard layout thumbnail: "/img/keyboard-review.jpg" --- Over the years, I customized my keyboard layout a lot (Bootmapper client then and QMK Toolbox now) and this is what I ended up using. It's pretty much HHKB layout with some QMK hacks on top. ![](/img/hhkb.jpg) ## Change CAPLOCK to CTRL `CAPLOCK` is useless to me so I change it to `CTRL`. I also set it up to use Mod-Tap key which will act as `CTRL` if I hold it but works as `BACKSPACE` if I tap it. If I press Fn+CAPLOCK, it's gonna be CAPLOCK as normal. Though I don't use it, I just want it to be consistent. This has a major benefit that I can do BACKSPACE by using my pinky finger instead of moving my right hand out of position. ## Double tap SHIFT for toggling CAPLOCK Using QMK's [tap dance feature](https://docs.qmk.fm/#/feature_tap_dance). It's right below the old CAPLOCK key, plus it makes sense (SHIFT and CAPLOCK). Also, double tapping is a lot easier than FN + CTRL key which requires 2 fingers. ## The SpaceFn layout The idea of SpaceFn is you will use SPACE as your layer switching key because it's easily accessible all the time when you're typing. You can read [more about SpaceFn here](https://geekhack.org/index.php?topic=51069.0). ![](/img/spacefn.jpg) While it sounds cool and all, the problem araises when you type fast, the SPACE key sometimes will be registered as layer switching key. You could reduce the wait for hold delay but I could never get accustom with that. This problem is quite severe becase SPACE is frequently used. So I ended changing this layout a bit to a what I call `EscFn` layout where the ESC key is the layer switching key. The ESC is now `LT(1, KC_ESC)`: hold for layer switching and still ESC on tap. I also enable `RETRO_TAPPING` so that in case I hold and release ESC without pressing another key, it will send ESC anyway. This is better because: - ESC is rarely used for key combo so it doesn't affect much. - ESC is less frequently used when typing. - ESC is close and easy to allocate. Well, not as good as SPACE because we still need to move our finger a bit but it's location is quite perfect. It's in the corner so you can always find it without looking at the keyboard. Also, I don't setup the whole thing, just the arrow cluster and HOME/END keys. Even though I'm using amVim with VSCode but there are still many apps which requires arrows for navigation. I still keep the WASD as arrow cluster for "backward compatible". --- --- date: "2018-11-15T00:00:00Z" tags: - Keyboard title: Favorite QMK hacks thumbnail: "/img/keyboard-review.jpg" --- ### [macOS media keys](https://github.com/qmk/qmk_firmware/blob/master/docs/keycodes.md) macOS media keys are supported on QMK: `KC__MUTE`, `KC__VOLUP`, `KC__VOLDOWN`, etc... This is essential if you're using macOS. ### [Grave Escape](https://github.com/qmk/qmk_firmware/blob/master/docs/feature_grave_esc.md) > If you're using a 60% keyboard, or any other layout with no F-row, you will have noticed that there is no dedicated Escape key. Grave Escape is a feature that allows you to share the grave key (` and ~) with Escape. This is godsend if you're using 60% keyboard. ### [Mod-Tap keys](https://github.com/qmk/qmk_firmware/blob/master/docs/feature_advanced_keycodes.md#mod-tap) > The Mod-Tap key MT(mod, kc) acts like a modifier when held, and a regular keycode when tapped. I use this to setup right shift to be `TILDE` on tap and `RSHIFT` on hold as normal. This feature is very useful for those modifiers like `CTRL`, `SHIFT` and `ALT` because you probably never tap those keys. ### [Space Cadet Shift](https://docs.qmk.fm/#/feature_space_cadet_shift) > Essentially, when you tap Left Shift on its own, you get an opening parenthesis; tap Right Shift on its own and you get the closing one. When held, the Shift keys function as normal. Yes, it's as cool as it sounds. I don't quite understand the need for this actually. It's cool still. ### [Space Cadet Shift Enter](https://docs.qmk.fm/#/feature_space_cadet_shift_enter) > Tap the Shift key on its own, and it behaves like Enter. When held, the Shift functions as normal. This one kinda make sense though because it's next to the ENTER key. To be honest, they could have use `SFT_T(KC_ENTER)` to achieve the similar result. ### KC_RGUI and KC_LGUI do not register Try holding Space + Backspace as you plug in the keyboard. [Credit to @braidn](https://github.com/qmk/qmk_firmware/issues/208) --- --- date: "2018-08-30T00:00:00Z" link: https://github.com/tuananh/camaro tags: - Open source title: Fastest way to transform XML to JSON in Node.js --- [camaro](https://github.com/tuananh/camaro) is an utility to transform XML to JSON using a template engine powered by XPath syntax which looks like this ![](/img/camaro-intro.jpg) Here are some benchmarks I ran with the sample data I usually have to deal with (XML data ranges from 1-10MB) camaro x 809 ops/sec ±1.51% (86 runs sampled) rapidx2j x 204 ops/sec ±1.22% (81 runs sampled) xml2json x 53.73 ops/sec ±0.58% (68 runs sampled) xml2js x 40.57 ops/sec ±7.59% (56 runs sampled) fast-xml-parser x 148 ops/sec ±3.43% (74 runs sampled) xml-js x 33.38 ops/sec ±6.69% (60 runs sampled) libxmljs x 127 ops/sec ±15.36% (50 runs sampled) And the benefits of `camaro` is that not only it's fast, it does the transformation for you as well. So you can just write a template and `camaro` will spit out the ready to use object using the schema you specified in the template. At the time when I wrote this, there were already many XML parsers but there ain't many which provide a way to support transformation. `camaro` was born from that constant need of transforming big XML files into JSON in Node.js. I was reading a blog post from Chad Austin about the fastest JSON parser. I was working exclusively with XML at the time so I asked him what about the fastest XML parser. He replied me that problem is already solved with [pugixml](https://github.com/zeux/pugixml) by Arseny Kapoulkine. pugixml looks very good from the benchmark. The only thing I can complain about it is the lack of streaming support, which I don't really need at the time so it's no big deal for me. It's fast. It supports XPath. It's very well-maintained. So a few dozens line of code for the transformation to glue pugixml with node and a couple of hours later, camaro was released. Just like that. --- --- date: "2018-08-06T00:00:00Z" tags: - Programming title: Sharding and IDs --- So I was going through [this post](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) from Instagram Engineering blog while researching for some sharding solutions. The solution is quite elegant and I decided to port this to MySQL. Turns out, it's harder than I thought since we're using a pretty dated MySQL version at work. There is no sequence, just AUTO_INCREMENTAL. In order to use the code snippet for PL/PGSQL, I would have to find a way to mimic `nextval` function. CREATE TABLE `sequence` ( `name` VARCHAR(100) NOT NULL, `increment` INT(11) NOT NULL DEFAULT 1, `min_value` INT(11) NOT NULL DEFAULT 1, `max_value` BIGINT(20) NOT NULL DEFAULT 9223372036854775807, `cur_value` BIGINT(20) DEFAULT 1, `cycle` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`name`) ) ENGINE=MyISAM; INSERT INTO sequence ( NAME, increment, min_value, max_value, cur_value ) VALUES ('my_sequence', 1, 0, 100, 0); DROP FUNCTION IF EXISTS nextval; DELIMITER $$ CREATE FUNCTION `nextval` (`seq_name` VARCHAR(100)) RETURNS BIGINT NOT DETERMINISTIC BEGIN DECLARE cur_val BIGINT; SELECT cur_value INTO cur_val FROM sequence WHERE NAME = seq_name; IF cur_val IS NOT NULL THEN UPDATE sequence SET cur_value = IF ( (cur_value + increment) > max_value OR (cur_value + increment) < min_value, IF ( cycle = TRUE, IF ( (cur_value + increment) > max_value, min_value, max_value ), NULL ), cur_value + increment ) WHERE NAME = seq_name; END IF; RETURN cur_val; END; $$ So the snippet above is what we use to mimic the `nextval` function. Quite troublesome huh? you can now call `SELECT nextval('my_sequence')` if you want to get next val of the sequence . Now, onto the generating id function. It's a pretty straight forward port from PL/PGSQL version. DROP FUNCTION IF EXISTS f_unique_id; DELIMITER $$ CREATE FUNCTION f_unique_id() RETURNS BIGINT BEGIN DECLARE our_epoch BIGINT; DECLARE seq_id BIGINT; DECLARE now_millis BIGINT; DECLARE shard_id INT; DECLARE result BIGINT; SET our_epoch = 1314220021721; SET shard_id = 5; SELECT nextval('my_sequence') % 1024 INTO seq_id; SELECT UNIX_TIMESTAMP() INTO now_millis; SET result = (now_millis - our_epoch) << 23; SET result = result | (shard_id << 10); SET result = result | (seq_id); RETURN result; END; $$ DELIMITER ; In order to generate an unique id with sharding info, you can do just this `select f_unique_id()` --- --- date: "2018-06-12T00:00:00Z" tags: - Redis title: Advanced filtering and sorting with redis (part 1) --- Set and sorted set are extremely powerful data types for filtering and sorting stuff with redis. ## Basic filtering Let's start with something simple. Usually filtering is just a matter of union and intersection. Let's say: filter all hotels that are 3 or 4 star and have both spa and pool. For this, we just have to create a set for each of the filter criteria and do union/intersection accordingly. Suppose we have the following data | hotel id | star rating | has spa | has pool | |----------|-------------|---------|----------| | 1 | 3 | yes | no | | 2 | 3 | yes | yes | | 3 | 3 | no | no | | 4 | 3 | no | no | | 5 | 4 | yes | yes | | 6 | 4 | no | no | | 7 | 4 | no | no | | 8 | 4 | no | no | Group those item by the property you want to do filter on sadd hotel:star:3 1 2 3 4 sadd hotel:star:4 5 6 7 8 sadd hotel:spa 1 2 5 sadd hotel:pool 2 5 As with the above example, it would be [UNION of (3,4 star sets)] INTERSECTION [ INTERSECTION of [spa, pool]] SUNIONSTORE 3or4star hotel:star:3 hotel:star:4 SINTERSTORE spaandpool hotel:spa hotel:pool SINTER 3or4star spaandpool # 2 5 And you got hotel id 2 and 5 as the result. ## Mutliple columns sorting Usually, in SQL, you can do multi columns sorting like this SELECT * FROM mytable ORDER BY col1 ASC, col2 ASC, col3 DESC How would you translate this logic to redis? Actually, this is not my idea but Josiah Calrson's (author of Redis in Action book). You can find [his blog post about this](http://www.dr-josiah.com/2013/10/multi-column-sql-like-sorting-in-redis.html) and demo implementation there as well. The basic idea is: `ZINTERSTORE` command supports `WEIGHTS` so we just have to calculate the weight for each column base on their order and sorting direction (ASC, DESC). If you know the range of the filter criteria in advance, you can save 1 round trip to redis to fetch it. for sort_col in sort: pipe.zrange(sort_col, 0, 0, withscores=True) pipe.zrange(sort_col, -1, -1, withscores=True) ranges = pipe.execute() *One thing to note is that this approach doesn't work well with non-integer values in mind. You can work around that by converting them to integer. For example, you can convert a non-integer values range from 0 to 10 with precision of 2 by multiplying the value with 100. Something like below:* function normalize(val, precision) { return Math.ceil(val * 10 ** precision) } --- --- date: "2018-05-23T00:00:00Z" tags: - GraphQL title: Notes on GraphQL --- Some personal notes while working with GraphQL - Only enable GraphiQL in development. - GraphQL is quite "chatty" by default. Use something like Facebook's [DataLoader](https://github.com/facebook/dataloader) for batching requests. - [DataLoader is not responsible for pagination](https://github.com/facebook/dataloader/issues/71). Implementation will be varied based on different backends. - [Apollo GraphQL](https://github.com/apollographql) is pretty awesome. Check out some of their open-source libraries. - Using `cacheKeyFn` for batching function (DataLoader) with multiple parameters. Related issue: [#75](https://github.com/facebook/dataloader/issues/75) - GraphQL is much more prone to attack because of its flexibility. You should implement auth/authorization as well as other protection mechanism like [query depth limit](https://github.com/stems/graphql-depth-limit), [query cost analysis](https://github.com/pa-bru/graphql-cost-analysis) or just skip those and read [this post](https://dev-blog.apollodata.com/securing-your-graphql-api-from-malicious-queries-16130a324a6b) - [Interfaces and Unions](https://medium.com/the-graphqlhub/graphql-tour-interfaces-and-unions-7dd5be35de0d) --- --- date: "2018-05-01T00:00:00Z" link: https://drive.google.com/file/d/1pqeZV2IuKne5SdsBTPxxBqwoAotNxJ-u/view?usp=sharing tags: - Talk - Kubernetes title: 'Kubernetes Meetup #2 slide' thumbnail: "/img/container-tech.jpg" --- [My slides](https://drive.google.com/file/d/1pqeZV2IuKne5SdsBTPxxBqwoAotNxJ-u/view?usp=sharing) from Kubernetes Meetup #2 organized by Docker Hanoi and CloudNativeVietnam. --- --- date: "2018-01-21T00:00:00Z" link: https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript tags: - Programming title: The Birth & Death of JavaScript --- Old but gold. > This science fiction / comedy / absurdist / completely serious talk traces the history of JavaScript, and programming in general, from 1995 until 2035. It's not pro- or anti-JavaScript; the language's flaws are discussed frankly, but its ultimate impact on the industry is tremendously positive. --- --- date: "2018-01-16T00:00:00Z" title: Advice to new managers --- Advice to new managers: - Earn trust by giving it - Inspire, don’t tell - Eat lunch with your team - Show their work matters - Be a player-coach - Feedback in private, praise in public - In victory, lead from back - In crisis, lead from front - Be the manager you wish you had via [Twitter](https://twitter.com/chrismaddern/status/953091090713890817) --- --- date: "2018-01-10T00:00:00Z" link: https://github.com/dinhviethoa/dejalu title: DejaLu - a new open source email client by Sparrow's author --- I downloaded the beta and gave it a try. At this stage, it's already a better email client than Airmail. Some notes: - Amazing startup speed. Why can't all apps be like this? - Nice and clean UI. Community themes could be a great feature to have but I don't mind not having it. - I specially like the conversation list view. It's just so clean. --- --- date: "2018-01-09T00:00:00Z" link: https://developers.google.com/web/fundamentals/primers/async-functions title: Series and parallel execution with async/await --- TIL series async function series() { await wait(500); // Wait 500ms… await wait(500); // …then wait another 500ms. return "done!"; } parallel async function parallel() { const wait1 = wait(500); // Start a 500ms timer asynchronously… const wait2 = wait(500); // …meaning this timer happens in parallel. await wait1; // Wait 500ms for the first timer… await wait2; // …by which time this timer has already finished. return "done!"; } --- --- date: "2018-01-01T00:00:00Z" title: '2017: year in review' --- ## Best year at work yet! - I worked on a project (with a several members of my team) to migrate our apps onto Kubernetes cluster since the beginning of 2017. We've been using Kubernetes in production since. - Convince and guide other teams to follow our initiative to migrate to Kubernetes. - 💸 Significantly reduce our AWS bills with the use of spot instances / spot fleet while maintaining high availability of the system. - ✌️ Got a new job!! Employer gave a counter offer matching compensation but I decided it's time to move on. Overall, I've set a concrete infrastructure for my company to move forward. I believe my team can step up and continue working on current projects. ## My one and only Being a father is overwhelming but certainly a great experience. Sleep depreviation sucks but any bad feelings seems to disappear when those little tiny hands hold my face and give me quick kiss on the cheek. ## More books I will not force myself to finish the book I'm not enjoying or learning from. I did this rigorously last year just for the sake of finishing the books. I also want to learn speed-read this year. Average readers read at speeds of around 250 words per minute with a typical comprehension of 60%. Imagine if you can read at 500 wpm, you can read twice as many books. It's truly an amazing skill to have. ## Talks I didn't give any talks last year. I would love to do 1 or 2 this year. Let's make it happen. 2018 is gonna be a great year! --- --- date: "2017-12-25T00:00:00Z" link: http://containersummit.io/events/nyc-2016/videos/building-containers-in-pure-bash-and-c tags: - Docker title: Building containers in pure Bash and C --- From 2016. Still very good talk by Jessie Frazelle. --- --- date: "2017-12-20T00:00:00Z" title: A better way to go through terminal command history --- In the past, I used to use `Ctrl+R` to search my terminal command history but it's unreliable. I couldn't wrap my head around how it search sometimes. Thanksfully, I was introduced to [fzf](https://github.com/junegunn/fzf) and it's has been a wonderful little gem. The power of `fzf` is much more than just searching through command history, depends on how creative you are. The one I show here is just an example of how powerful `fzf` is. Basically, it can be pipe to just anything and fuzzy search that. fh () { print -z $( ([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s --tac | sed 's/ *[0-9]* *//') } Now go `brew install fzf`, add the above function and thanks me later 😁 --- --- date: "2017-12-15T00:00:00Z" link: http://sysadvent.blogspot.com/2017/12/day-14-pets-vs-cattle-prods-silence-of.html tags: - AWS title: The silence of the Lambda --- A lot has changed since last I look at it. Still a bit awkward but the toolings around Lambda have significantly improved. Related: - [AWS Lambda reserved concurrency](https://blog.symphonia.io/aws-lambda-reserved-concurrency-f2c3a32b9f1d) - [Safe Lambda deployment](https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst) --- --- date: "2017-12-14T00:00:00Z" title: Traits of a good leader --- Found this on HackerNews. Very much on point, though I don't quite agree with the last item. 1. You have to have your people's back, this is the most important thing... be there for them, insulate them from problems and management stupidity and always fight for them. 2. Lead by example, never ask them to do something you won't do yourself. 3. Communicate, I have booked on afternoon a week from 14:00pm till 16:00 and more to just talk with my team and discuss everything from work, to weather, sports, to bitch and moan against the company, etc... 4. Get together as much as you can on a real "team building exercise" - the whole team in another city for at least 2 days with a great party and lots of eating and drinking on company dime... --- --- date: "2017-11-18T00:00:00Z" link: https://github.com/tuananh/node-prune tags: - Open source title: node-prune --- > Easily pruning unneeded files from `node_modules` A npm package to clean up `node_modules` folder to remove unnecessary files on production. Use cases: - optimize sizes for aws lambda - optimize sizes for docker build using multi stage docker [Featured on npmaddict](https://npmaddict.com/#/days/2017-11-24). --- --- date: "2017-11-17T00:00:00Z" link: https://github.com/tuananh/kompression tags: - Open source - Node.js title: kompression - koa compression middleware with support for brotli --- This is my fork of `koa-compress` that add supports for brotli compression. Available on [npm](https://npm.im/kompression). --- --- date: "2017-10-27T00:00:00Z" link: https://blog.jessfraz.com/post/docker-containers-on-the-desktop/ tags: - Docker title: Docker Containers on the Desktop --- Great idea. I never thought of Docker containers this way because I totally forgot that I can always mount the config to the container. This totally changes my dev environment setup. $ docker run -it \ -v /etc/localtime:/etc/localtime \ -v $HOME/.irssi:/home/user/.irssi \ # mounts irssi config in container --read-only \ # cool new feature in 1.5 --name irssi \ jess/irssi --- --- date: "2017-10-15T00:00:00Z" link: https://hbr.org/2016/12/if-your-boss-could-do-your-job-youre-more-likely-to-be-happy-at-work title: If Your Boss Could Do Your Job, You’re More Likely to Be Happy at Work thumbnail: "/img/competent-boss.jpg" --- > The benefit of having a highly competent boss is easily the largest positive influence on a typical worker’s level of job satisfaction Related: [This is why we have working managers at Basecamp](https://m.signalvnoise.com/this-is-why-we-have-working-managers-at-basecamp-and-why-microsoft-and-apple-stumbled-when-they-ac8e1ebd444c) --- --- date: "2017-10-13T00:00:00Z" tags: - Kubernetes title: Kubernetes-hosted application checklist (part 2) thumbnail: "/img/server-rack.jpg" --- This part is about how to define constraint to the scheduler on where/how you want your app container to be deployed on the k8s cluster. ### Node selector Simpleast form of constraint for pod placement. You attach labels to nodes and you specify `nodeSelector` in your pod configuration. #### When to use * you want to deploy redis instance to memory-optimized (R3, R4) instance group for example. ### Affinity and anti-affinity Affinity and anti-affinity is like `nodeSelector` but much more advanced, with more type of constraints you can apply to the default scheduler. * the language is more expressive (not just “AND of exact match”) * you can indicate that the rule is “soft”/”preference” rather than a hard requirement, so if the scheduler can’t satisfy it, the pod will still be scheduled * you can constrain against labels on other pods running on the node (or other topological domain), rather than against labels on the node itself, which allows rules about which pods can and cannot be co-located `requiredDuringSchedulingIgnoredDuringExecution` is the hard type and `preferredDuringSchedulingIgnoredDuringExecution` is the soft/preference type. In short, affinity rules define rules/preferences to where a pod deploys and anti-affinity is the opposite. #### When to use * affinity and anti-affinity should be used when necessary only. It has a side effect of reducing the speed of deployment. * affinity use case example: web server and redis server should be in the same node * anti-affinity example: 3 redis slaves should not be deployed in the same node. --- --- date: "2017-10-13T00:00:00Z" tags: - Kubernetes title: Kubernetes-hosted application checklist (part 1) thumbnail: "/img/server-rack.jpg" --- At work, we've been running Kubernetes (k8s) in production for almost 1 year. During this time, I've learnt a few best practices for designing and deploying an application hosted on k8s. I thought I might share it today and hopefully it will be useful to newbie like me. ### Liveness and readiness probes - Liveness probe: check whether your app is running - Readiness probe: check whether your app is ready to accept incoming request Liveness probe is only check after the readiness probe passes. If your app does not support liveness probe, k8s won't be able to know when to restart your app container and in the event your process crashes, it will stay like that while k8s still directing traffic to it. If your app takes some time to bootstrap, you need to define readiness probe as well. Otherwise, requests will be direct to your app container even if the container is not yet ready to service. Usually, I just make a single API endpoint for both liveness and readiness probes. Eg. if my app requires database and Redis service to be able to work, then in my health check API, I will simply check if the database connection and redis service are ready. try { const status = await Promise.all([redis.ping(), knex.select(1)]) ctx.body = 'ok' } catch (err) { ctx.throw(500, 'not ok') } ### Graceful termination When an app get terminated, it will receive `SIGTERM` and `SIGKILL` from k8s. The app must be able to handle such signal and terminate itself gracefully. The flow is like this - container process receives `SIGTERM` signal. - if you don't handle such signal and your app is still running, `SIGKILL` is sent. - container get deleted. Your app should handle `SIGTERM` and should not get to the `SIGKILL` step. Example of this would be something like below: process.on('SIGTERM', () => { state.isShutdown = true initiateGracefulShutdown() }) function initiateGracefulShutdown() { knex.destroy(err => { process.exit(err ? 1 : 0) }) } Also, the app should start returning error on liveness probe. --- --- date: "2017-10-04T00:00:00Z" tags: - Node.js - Docker title: Minimal Node.js docker container --- Bitnami recently releases a `prod` version of their `bitnami-docker-node` with much smaller size due to stripping a bunch of unncessary stuff for runtime. If your app does not require compiling native modules, you can use it as is. No changes required. However, if you do need to compile native modules, you can still use their development image as builder and copy stuff over to `prod` image after. I try with one of my app and the final image size reduce from 333 MB down to just 56 MB 💪 !! All these without the sacrify of using alpine-based image. Please note that this is the size reported by Amazon Cloud Registry so probably compressed size. I don't build image locally often. _update: the uncompressed size of my app is 707MB before and 192 MB after._ FROM bitnami/node:8.6.0-r1 as builder RUN mkdir -p /usr/src/app/my-app WORKDIR /usr/src/app/my-app COPY package.json /usr/src/app/my-app RUN npm install --production --unsafe COPY . /usr/src/app/my-app FROM bitnami/node:8.6.0-r1-prod RUN mkdir -p /app/my-app WORKDIR /app/my-app COPY --from=builder /usr/src/app/my-app . EXPOSE 3000 CMD ["npm", "start"] --- --- date: "2017-09-05T00:00:00Z" link: http://canihaznonprivilegedcontainers.info/ tags: - Docker title: Non-privileged containers FTW --- FROM ubuntu:latest RUN useradd -u 10001 scratchuser FROM scratch COPY dosomething /dosomething COPY --from=0 /etc/passwd /etc/passwd USER scratchuser ENTRYPOINT ["/dosomething"] Quite innovative use of multi stage docker build. Of course, you can create a `passwd` file yourself but this one seems much rather interesting. --- --- date: "2017-08-22T00:00:00Z" title: Recent Node.js TSC fuss --- * [meta: vote regarding Rod's status on the TSC](https://github.com/nodejs/TSC/issues/310) * Some remove information on what Rod did from the above link ([image](/img/nodejs-tsc.jpg)) * new [Node.js fork ayojs](https://github.com/ayojs/ayo) regarding this issue --- --- date: "2017-06-28T00:00:00Z" tags: - Docker title: node-pre-gyp and CI --- Note to self: > When developing new feature for Node.js native module and using `node-pre-gyp`, make sure you pump version higher so that `node-pre-gyp` will not pull the prebuilt binary. --- --- date: "2017-06-27T00:00:00Z" link: https://github.com/tldr-pages/tldr title: tldr --- A better man page. This is insanely useful 👍 tar Archiving utility. Often combined with a compression method, such as gzip or bzip. - Create an archive from files: tar cf target.tar file1 file2 file3 - Create a gzipped archive: tar czf target.tar.gz file1 file2 file3 - Extract an archive in a target folder: tar xf source.tar -C folder - Extract a gzipped archive in the current directory: tar xzf source.tar.gz - Extract a bzipped archive in the current directory: tar xjf source.tar.bz2 - Create a compressed archive, using archive suffix to determine the compression program: tar caf target.tar.xz file1 file2 file3 - List the contents of a tar file: tar tvf source.tar --- --- date: "2017-06-26T00:00:00Z" title: The power of 2 random choices --- * [Birthday paradox](https://en.wikipedia.org/wiki/Birthday_problem) * [The Power of Two Random Choices: A Survey of Techniques and Results by Mitzenmacher, Richa, and Sitaraman](http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf) * [Cache eviction: when are randomized algorithms better than LRU?](https://danluu.com/2choices-eviction/) --- --- date: "2017-06-26T00:00:00Z" tags: - WebAssembly title: Getting started with WebAssembly --- * [WebAssembly Binary Toolkit](https://github.com/mafintosh/webassembly-binary-toolkit) - Easiest way to setup WebAssembly binary toolkit. Or if you prefer to [manually install it](https://github.com/WebAssembly/wabt/). * [wat2js](https://github.com/mafintosh/wast2js) - Compile WebAssembly .wat files to a common js module * [WebAssembly spec](http://webassembly.github.io/spec/) - WebAssembly Specification Some examples modules * [siphash24](https://github.com/mafintosh/siphash24) - SipHash (2-4) implemented in pure Javascript and WebAssembly. * [blake2b](https://github.com/mafintosh/blake2b-wasm) - Blake2b implemented in WASM * [xsalsa20](https://github.com/mafintosh/xsalsa20) - XSalsa20 implemented in Javascript and WebAssembly *Follow [Mathias Buus on GitHub](https://github.com/mafintosh). He's has published quite a few interesting things about WebAssembly.* --- --- date: "2017-05-31T00:00:00Z" link: https://github.com/tuananh/camaro tags: - Open source title: camaro --- Successor to [xpath-object-transform](https://github.com/tuananh/xpath-object-transform) > camaro is an utility to transform XML to JSON, using Node.js binding to native XML parser pugixml, one of the fastest XML parser around. * Faster. A lot faster. * Building time is also faster. * Mostly backward compatible with `xpath-object-transform`. I haven't cover all the cases though. * pre-built binaries included for some distro. Will include more in the future. Featured on [NodeWeekly #192](http://nodeweekly.com/issues/192) and [npm addict](https://npmaddict.com/) --- --- date: "2017-05-29T00:00:00Z" link: http://www.aosabook.org/en/posa/parsing-xml-at-the-speed-of-light.html tags: - Open source title: Parsing XML at the Speed of Light --- > This chapter describes various performance tricks that allowed the author to write a very high-performing parser in C++: pugixml. While the techniques were used for an XML parser, most of them can be applied to parsers of other formats or even unrelated software (e.g., memory management algorithms are widely applicable beyond parsers). Found out about this gem, recommended by the author of ["Writing a Really, Really Fast JSON Parser"](https://chadaustin.me/2017/05/writing-a-really-really-fast-json-parser/). This is a really good post as well. --- --- date: "2017-05-22T00:00:00Z" link: http://redisearch.io/Quick_Start/ tags: - Redis title: Redisearch - full text search with Redis --- Very young but interesting project. Might save you from introducing something new your project just for full text search (*chances are you probably already have Redis in your tech stack*) Here's [a dockerfile](https://github.com/tuananh/Dockerfile/tree/master/redisearch) for Redis 4.0 RC3 and Redisearch 0.16 for you to fiddle with. --- --- date: "2017-04-16T00:00:00Z" link: https://hub.docker.com/r/bitnami/minideb/ tags: - Docker title: minideb - a small base image based on Debian --- Selling points: * Small * The image is based on glibc for wide compatibility * Using apt package manager for access to large number of packages * Quicker security updates Even though there are many complaints about `glibc`, it's still very widely-adopted. I would hate to debug building libraries with `musl-libc`. It's just not worth it. --- --- date: "2017-04-13T00:00:00Z" tags: - Docker title: Using alpine as base Docker image --- I recently updated all of my personal Dockerfiles that I have for multiple purposes to use `alpine` as base image. Prior this, I just use `ubuntu` as the base image and don't have much care about built-images size. However, using Kubernetes, having small images size can make rolling out update speed much faster. Some tips for reducing Docker image size that I found during my research: - Using smaller base image (alpine, busybox, etc..) - Remove unnecessary dependencies that you use for compiling stuff after done. (Also remove cache) - Use few layers as possible. However, I don't think you should do it blindly just for the shake of small image size and destroy readability. I prefer to keep it simple and clean. Optimize it later for building only. --- --- date: "2017-04-11T00:00:00Z" link: https://github.com/tuananh/kubernetes-twemproxy tags: - Kubernetes title: Running twemproxy on kubernetes --- How to setup twemproxy running on kubernetes. TODO: - Convert this into helm chart. --- --- date: "2017-04-04T00:00:00Z" tags: - Kubernetes title: Setting up traefik as Ingress controller for Kubernetes --- Just my own experience setting up traefik as Ingress controller on Kubernetes. ### Install helm ``` brew install kubernetes-helm ``` ### Init helm ``` helm init ``` ### Install traefik chart with helm Download the default `values.yaml` file and edit it depends on your needs. Then issue the below command. I want to install it to `kube-system` namespace hence the `--namespace kube-system`. ``` helm install --name my-traefik --namespace kube-system --values values.yaml stable/traefik ``` If you make a mistake and want to remove it ``` helm delete --purge my-traefik ``` `--purge` if you want to reuse the name previously. Otherwise, helm will complain `release xxx already exists`. ### Update your app You will have to create an Ingress, a service and a deployment. - Ingress is a rule to help you setup routing traffic from a domain to your cluster service name - Service will expose your pods to be accessible in the cluster by selector (see example) - Deployment is well .. your app deployment. It will be a whole lot easier if you see [this cheese.yaml file](https://gist.github.com/tuananh/22e0fec5e43f6f56190a10df4822ed00) Download the file and `kubectl create -f traefik.yaml` to deploy it to your cluster. In the example above, i will use the domain `stilton.example.com` ### Update DNS Create a CNAME record of `stilton.example.com` to point to your traefik's ELB public address. ### Profit! If you have traefik dashboard enabled, you should see new ingress and the pods it distributes load to on the web. PS: You don't necessarily use `helm`. It just make installing stuff a lot easier. --- --- date: "2017-04-03T00:00:00Z" link: https://github.com/nodejs/node/commit/56e881d0b0b2997b518753cb627eb3b50eeb6f62 tags: - Node.js title: 'n-api: add support for abi stable module API' --- > Add support for abi stable module API (N-API) as "Experimental feature". The goal of this API is to provide a stable Node API for native module developers. N-API aims to provide ABI compatibility guarantees across different Node versions and also across different Node VMs - allowing N-API enabled native modules to just work across different versions and flavors of Node.js without recompilation. Great news for native module developers :) --- --- date: "2017-04-01T00:00:00Z" link: https://github.com/CodisLabs/codis tags: - Redis title: codis - proxy based redis cluster --- > Proxy based Redis cluster solution supporting pipeline and scaling dynamically Seems like more feature-riched than [twemproxy](https://github.com/twitter/twemproxy) or [dynomite](https://github.com/Netflix/dynomite) however the documentation seems poor and community is smaller. Just something to keep in mind. --- --- date: "2017-03-22T00:00:00Z" link: https://redislabs.com/blog/redis-as-a-json-store/ tags: - Redis title: Redis as a JSON store --- > a Redis module that provides native JSON capabilities – get it from the [GitHub repository](https://github.com/redislabsmodules/rejson) or read the docs online. --- --- date: "2017-02-15T00:00:00Z" link: https://aws.amazon.com/blogs/aws/focusing-on-spot-instances-lets-talk-about-best-practices/ tags: - Kubernetes - AWS title: Spot instances best practices --- TLDR - Build Price-Aware Applications - Check the Price History: In general, picking older generations of instances will result in lower net prices and fewer interruptions. - Use Multiple Capacity Pools: By having the ability to run across multiple pools, you reduce your application’s sensitivity to price spikes that affect a pool or two (in general, there is very little correlation between prices in different capacity pools). For example, if you run in five different pools your price swings and interruptions can be cut by 80%. --- --- date: "2017-01-11T00:00:00Z" tags: - Kubernetes title: Debugging why k8s autoscaler wouldn't scale down --- Symptom: autoscaler works (it can scale up) but for some reasons, it doesn't scale down after the load goes away. I spent sometimes debugging and turns out, it's not really a bug per se. More of a bad luck pod placement on my Kubernetes cluster. I first added `--v=4` to get more verbose logging in `cluster-autoscaler` and watch `kubectl get logs -f cluster-autoscaler-xxx`. I notice this line from the logs cannot be removed: non-deamons set, non-mirrored, kube-system pod present: tiller-deploy-aydsfy This node is in fact under-ultilized but there is a `non-deamons set, non-mirrored, kube-system pod` presented, that's why it can't be removed. `tiller-deploy` is a deployment that comes with Helm package manager. So it seems I just have to migrate the pod to another node and it's gonna be fine. You can also read more on [how cluster-autoscaler works here on GitHub](https://github.com/kubernetes/contrib/tree/master/cluster-autoscaler) --- --- date: "2016-12-29T00:00:00Z" link: https://github.com/tuananh/smaz.js title: smaz.js - a Node.js module binding for smaz --- [smaz.js](https://github.com/tuananh/smaz.js) is smaz (short string compression from antirez) binding for v8 Javascript engine. I've just released it on GitHub. My very first attempt at creating native library binding for Node.js. --- --- date: "2016-12-27T00:00:00Z" link: https://github.com/callstats-io/fluentd-kubernetes-cloudwatch tags: - Kubernetes title: Setting up fluentd log forwarding from Kubernetes to AWS Cloudwatch Logs --- > Fluentd Docker image to send Kuberntes logs to CloudWatch Very easy to setup. Good option for centralized logging if all of your infrastructures are already in AWS. echo -n "accesskeyhere" > aws_access_key echo -n "secretkeyhere" > aws_secret_key kubectl create secret --namespace=kube-system generic fluentd-secrets --from-file=aws_access_key --from-file=aws_secret_key kubectl apply -f fluentd-cloudwatch-daemonset.yaml *On a side note, I think i will need to move fluend configuration file to secret as I just want to collect logs from certain namespace/filter.* --- --- date: "2016-12-25T00:00:00Z" link: https://github.com/mumoshu/kube-spot-termination-notice-handler tags: - Kubernetes title: Kubernetes spot termination notice handler --- A DaemonSet to be run on node instance and keep polling `http://169.254.169.254/latest/meta-data/spot/termination-time` for termination notice. The daemonset will poll every 5 seconds which will give you approx 2 minutes to drain the spot node and migrate pods to another node. --- --- date: "2016-12-25T00:00:00Z" link: https://www.youtube.com/watch?v=IYcL0Un1io0 tags: - Kubernetes title: 'GopherCon 2016: Kelsey Hightower - Building a custom Kubernetes scheduler' --- How to build a custom Kubernetes scheduler by Mr. Kubernetes --- --- date: "2016-12-23T00:00:00Z" title: Fix Terminal no longer uses keychain in MacOS Sierra --- Since Sierra, I got prompted for my ssh key password everytime. After digging a bit, it seems Apple just changes it recently. > On macOS, specifies whether the system should search for passphrases in the user's keychain when attempting to use a particular key. When the passphrase is provided by the user, this option also specifies whether the passphrase should be stored into the keychain once it has been verified to be correct. The argument must be 'yes' or 'no'. The default is 'no'. In order to fix this, you just have to enable `UseKeychain` for every hosts by adding these lines into your `.ssh/config` file Host * AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_rsa Alternatively, you can add `ssh-add -A` into your `.bashrc` or `.zshrc`. --- --- date: "2016-12-17T00:00:00Z" link: https://railsadventures.wordpress.com/2015/12/06/why-we-chose-kubernetes-over-ecs/ tags: - Kubernetes title: Why we choose Kubernetes over ECS thumbnail: "/img/k8s-vs-ecs.jpg" --- A bit outdated post but many points stay valid. --- --- date: "2016-12-15T00:00:00Z" link: https://zachholman.com/posts/goddamn-adults title: Bring in the Goddamn Adults Already --- > Time and time again, the young startup promotes their longest-tenured young engineer to become CTO of their 20-something startup. And it makes sense on the surface, because it’s their “best” engineer. And why not? They’ve been there for so long that they know the system they’ve built more than anyone else. > But now they have two problems: they lose their “best” engineer, and on top of that, they gain what’s probably a shit manager. --- --- date: "2016-12-11T00:00:00Z" tags: - Redis title: Using ioredis with twemproxy --- > Twemproxy is a fast/lightweight proxy for memcached and redis. Not all Redis commands are supported. You can take a look at the [list of supported commands on Github](https://github.com/twitter/twemproxy/blob/master/notes/redis.md) --- --- date: "2016-12-08T00:00:00Z" link: https://github.com/asobti/kube-monkey tags: - Kubernetes title: kube-monkey thumbnail: "/img/kubemonkey.jpg" --- > kube-monkey is an implementation of Netflix's Chaos Monkey for Kubernetes clusters. It randomly deletes Kubernetes pods in the cluster encouraging and validating the development of failure-resilient services. Netflix's Chaos Monkey for Kubernetes --- --- date: "2016-11-20T00:00:00Z" link: https://github.com/jetstack/kube-lego tags: - Kubernetes title: Automate Let's Encrypt certificate genernation for Kubernetes Ingress thumbnail: "/img/ssl-cert.jpg" --- > Kube-Lego automatically requests certificates for Kubernetes Ingress resources from Let's Encrypt You can find complete example how to use this with GCE [here](https://github.com/jetstack/kube-lego/tree/master/examples/gce) --- --- date: "2016-11-16T00:00:00Z" link: https://github.com/dbcli/mycli title: mycli --- > A Terminal Client for MySQL with AutoCompletion and Syntax Highlighting. I can't recommend this enough! Insanely useful tool. --- --- date: "2016-11-15T00:00:00Z" link: https://mesosphere.com/blog/2015/11/12/sharing-secret-data-in-kubernetes/ tags: - Kubernetes title: Sharing secret data in Kubernetes --- - I've seen people bundle config file within source code. - I've seen people bundle config when building Docker images. PLEASE DON'T. Just use secrets / environment variables. Here is a [very detail tutorial](https://mesosphere.com/blog/2015/11/12/sharing-secret-data-in-kubernetes/) on how to share secret data in Kubernetes. --- --- date: "2016-11-11T00:00:00Z" link: https://www.distelli.com/ tags: - Kubernetes title: Distelli - Your DevOps Dashboard for Kubernetes --- > Your DevOps Dashboard for Kubernetes I've been looking for CI/DI that would complete the Kubernetes setup. This looks like a good fit for it. --- --- date: "2016-11-08T00:00:00Z" description: How to setup squid proxy to bypass IP whitelisting title: Using squid proxy to bypass 3rd party API IP whitelisting --- At work, I have to work with many 3rd party supplier API which requires IP whitelisting. This is becoming an issue when we need to autoscale using multiple Kubernetes nodes. There are several ways to deal with this - Use NAT gateway to forward all outgoing traffic to the gateway - Use a proxy like Squid I went with Squid since it's much easier. Tinkering with network setting is nightmare for me. You can follow the [tutorial here on Google Cloud Documentation](https://cloud.google.com/compute/docs/networking#proxyvm) and then export these environment variables below in your Kubernetes nodes / Docker container. export http_proxy="http://:3128" export https_proxy="http://:3128" export ftp_proxy="http://:3128" export no_proxy="169.254.169.254,metadata,metadata.google.internal" You can verify if it's working properly by checking the public IP address of the node afterward by `curl ifconfig.me`. Also, package like `request` does [respect `HTTP_PROXY` and `HTTPS_PROXY`](https://www.npmjs.com/package/request#controlling-proxy-behaviour-using-environment-variables) so you probably don't have to make any changes to the existing code base. --- --- date: "2016-10-28T00:00:00Z" tags: - Kubernetes title: Getting started with Kubernetes --- Just some of my notes while learning about Kubernetes. I use Google Compute Engine to install mine. ### Installation To install Kubernetes, it's as easy as copy and paste the below command curl -sS https://get.k8s.io | bash If you want to customize some default options, you can edit environment variables curl -sS https://get.k8s.io | MULTIZONE=true KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=asia-east1-b NUM_NODES=4 bash There're more environment variables that you can take a look in `kubernetes/cluster/gce/config-default.sh` It's recommended to export it to environment instead of passing it to the command as above as taking the cluster down will be easier. export KUBERNETES_PROVIDER=gce export KUBE_GCE_ZONE=asia-east1-b export NODE_SIZE=n1-highcpu-2 export MULTIZONE=true export NUM_NODES=2 export KUBE_AUTOSCALER_MIN_NODES=2 export KUBE_AUTOSCALER_MAX_NODES=10 export KUBE_ENABLE_CLUSTER_AUTOSCALER=true export PREEMPTIBLE_NODE=true ### Add more nodes to cluster KUBE_USE_EXISTING_MASTER=true KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=asia-east1-b NUM_NODES=2 ./kube-up.sh ### Bring down cluster KUBERNETES_PROVIDER=gce KUBE_GCE_ZONE=asia-east1-b ./kube-down.sh ### Deploying applications on Kubernetes We use Docker at work and deploying Docker containers in Kubernetes is a breeze. docker build -t gcr.io/$PROJECT_ID/app-name:v1 . gcloud docker push gcr.io/$PROJECT_ID/app-name:v1 # update kubectl rolling-update --image=gcr.io/$PROJECT_ID/app-name:v2 Expose the service to external kubectl expose deployment --type="LoadBalancer" ### Enable autoscale kubectl autoscale rc --min 3 --max=10 --cpu-percent=60 --- --- date: "2016-10-25T00:00:00Z" tags: - Tiling window manager title: The state of tiling window manager for OS X --- - [Slate](https://github.com/jigish/slate) - [Amethyst](https://github.com/ianyh/Amethyst) - [Hammerspoon](https://github.com/Hammerspoon/hammerspoon) - [kwm](https://github.com/koekeishiya/kwm) kwm seems to be the best - feature-wise but it requires too many hacking to be done. I've given up at some point and just went back with Amethyst. Slate and Hammerspoon aren't really what I'm looking for. --- --- date: "2016-10-25T00:00:00Z" link: https://nodesource.com/blog/the-definitive-guide-to-the-first-three-days-of-yarn-and-npm/ title: Should I use yarn? --- > Myles Borins (@thealphanerd) recently ran citgm with yarn, and shared the results. It was 25 minutes faster than npm, but 20 modules failed to install. > Yarn is not a drop in replacement. Some areas where issues arise: - Reliance on npm-shrinkwrap.json - Private modules on npm - Modules backed by self-signed certificates - Exotic dependencies declarations like .zip or shorthand urls - Edge case issues on specific OSes, like Windows - Pre- and post-script hooks don't work like they do in npm - Scripts that rely on npm environmental variables --- --- date: "2016-10-21T00:00:00Z" link: https://github.com/nodejs/node/wiki/Breaking-changes-between-v4-LTS-and-v6-LTS title: Breaking changes between v4 LTS and v6 LTS --- I've just migrated 4 big applications at work from Node.js v4 to Node.js v6 LTS. 3 of them require some little changes and 1 with no changes at all since it's using Babeljs. You can view the complete list of breaking changes from v4 to v6 [here on GitHub](https://github.com/nodejs/node/wiki/Breaking-changes-between-v4-LTS-and-v6-LTS). --- --- date: "2016-10-21T00:00:00Z" title: AMP'ed --- This site is now [AMP-friendly](https://github.com/ampproject/amphtml) The cached version of the page is up at [this url](https://cdn.ampproject.org/c/s/tuananh.org/2014/09/01/a-poor-mans-seedbox-for-15-dollars-a-year/) below, remove the `s` if your site isn't on HTTPS. https://cdn.ampproject.org/c/s/tuananh.org/2014/09/01/a-poor-mans-seedbox-for-15-dollars-a-year/ --- --- date: "2016-10-20T00:00:00Z" title: Xbox Scorpio vs PS Neo --- If this were true, I will definitely upgrade to Scorpio. The difference in GPU power is huge!! --- --- date: "2016-10-20T00:00:00Z" link: https://github.com/owenversteeg/min title: mincss --- > the world's smallest (995 bytes) CSS framework http://mincss.com In an attempt to make my website fast again. Custom font no more. --- --- date: "2016-10-17T00:00:00Z" title: How to import csv file in MySQL --- If your MySQL server started with `--secure-file-priv` option, you will have to move your csv file to that specific folder first. To check where that folder is, first run `SHOW VARIABLES LIKE "secure_file_priv";` Move your csv file to that folder and issue the following command in MySQL console. Remember to replace `/var/lib/mysql-files/` with your `secure_file_priv` path variable. ~~~ LOAD DATA INFILE "/var/lib/mysql-files/data.csv" \ INTO TABLE my_table \ COLUMNS TERMINATED BY ',' \ OPTIONALLY ENCLOSED BY '"' \ ESCAPED BY '"' \ LINES TERMINATED BY '\n' \ IGNORE 1 LINES; ~~~ --- --- date: "2016-10-17T00:00:00Z" link: https://medium.com/@tjholowaychuk/dos-and-don-ts-of-aws-lambda-7dfcab7ad115#.c4nyr653a title: Do’s and Don’ts of AWS Lambda --- TJ Holowaychuk writes about Do and Don'ts of AWS Lambda. --- --- date: "2016-09-07T00:00:00Z" link: https://drive.google.com/file/d/0B5lnD5gfVd69bXBOXzh0SUp5UXM/view?usp=sharing tags: - Book title: No More Vietnam --- Nixon's retrospective on Vietnam war, 10 years later. --- --- date: "2016-09-05T00:00:00Z" title: Mildly interesting npm modules --- Not in any particular order - [errno](https://github.com/rvagg/node-errno) by [rvagg](https://github.com/rvagg) - [worker-farm](https://github.com/rvagg/node-worker-farm) by [rvagg](https://github.com/rvagg) - [node-timsort](https://github.com/mziccard/node-timsort) - [microlock](https://github.com/mziccard/node-timsort) - [thenby.js](https://github.com/Teun/thenBy.js) > thenBy is a javascript micro library that helps sorting arrays on multiple keys - [lz-string](https://github.com/pieroxy/lz-string) - [node-expat](https://github.com/astro/node-expat) > You use Node.js for speed? You process XML streams? Then you want the fastest XML parser: libexpat! - [Fuse](https://github.com/krisk/Fuse) > Lightweight fuzzy-search, in JavaScript, with zero dependencies - [js ex machina](https://github.com/ifandelse/machina.js) > js ex machina - finite state machines in JavaScript http://machina-js.org/ --- --- date: "2016-08-29T00:00:00Z" title: AWS Lambda --- So a few months back, when I read of AWS lambda, I instantly fell in love with it though not having any immediate use with it. AWS lambda is perfect for those wish to build microservices. There are many benefits of doing this such as independent deployment, security and scability. But I'm not going to talk about this within this post. This post is merely my experience with AWS Lambda. Enter AWS Lambda: great product, works like magic and requires no dev ops which is a huge plus at where I work. However, there are a few cons: ### Testing locally There is no easy way to do this locally. Looks like there isn't many alternative out there except maybe [lambda-local](https://www.npmjs.com/package/lambda-local) ### Building native packages I've yet to come across a service that build native node.js package for AWS lambda deployment. For now, I have to spin up a EC2 instance, install nodejs and gcc just to build a package, zip it and download it to my local machine in order to prepare for deployment. ### Error handling This isn't a problem with AWS lambda particular but more like with microservice. However, AWS lambda doesn't introduce any solution to deal with this either. --- As much as I like to use aws lambda in production, i haven't yet to see any use cases for that at work to put all these in real world test. I'm sure the future of "function as a serivce" is much more and I would love to go into greater details about it in future posts if I have a chance to put it to production. For now though.. Phew --- --- date: "2016-07-27T00:00:00Z" link: https://flynn.io/blog/one-point-oh title: Flynn 1.0 --- Flynn 1.0 released. Another thing to consider aside with Deis. I've tried this before but it was far from production-ready back then. Hopefully that has changed. --- --- date: "2016-05-10T00:00:00Z" link: https://convox.com title: Open-source PaaS on AWS --- Something to consider along side with deis. To be honest, deis offers more choice of deployment ranging from AWS, DigitalOcean, etc.. to bare metal. The choice is complete yours. Also, you don't get vendor lockdown to AWS. --- --- date: "2016-05-09T00:00:00Z" link: https://mobile.twitter.com/ProductHuntKeys/status/727294358400176129 title: Work hard and go home --- I don't get ping pong table or hanging out during workday. Believe it or not, many of us have family and prefer to spend time with them after work. T-shirt, ping pong table, or free lunch, etc..., I am way pass that. --- --- date: "2016-05-05T00:00:00Z" link: https://github.com/sindresorhus/refined-twitter/blob/master/readme.md title: Refined Twitter --- > An extension that makes Chrome always use the mobile web version of Twitter, which is much faster and better looking than the old desktop web version. When you open a link that would normally be to desktop Twitter, this extension redirects you to the mobile web version and makes it wider. Good thing about this is when clicking back button in browser, mobile Twitter get you back to where you were, unlike the desktop version. They also offer a desktop version - [Anatine](https://github.com/sindresorhus/anatine). --- --- date: "2016-04-19T00:00:00Z" title: Geo dynamic upstream with nginx --- I recently had to setup nginx as load balancer at work. Due to the reason that one of our customer is in China, we have to add few China servers to our load balancer and route everything from China to those instead. Turn out it's quite easy to do it with nginx and geo package. You will need `nginx-full` or compile nginx yourself with geo package. After setting nginx with geoip. Create a new configuration in `site-available`, symlink to `site-enabled` to enable it. The content of the fie should look like this ~~~ upstream first_upstream { ip_hash; # for sticky session server ; server ; } upstream second_upstream { ip_hash; server ; server ; } map $geoip_country_code $preferred_upstream { default first_upstream; CN second_upstream; # use second_upstream for those from China (CN) } server { ... location / { proxy_pass http://$preferred_upstream/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ... } ~~~ --- --- date: "2016-04-18T00:00:00Z" link: https://developer.atlassian.com/blog/2015/11/scripting-with-node/ title: Build command line tools with Node.js --- Everything you need to know about [building command line tools with Node.js](https://developer.atlassian.com/blog/2015/11/scripting-with-node/). --- --- date: "2016-04-15T00:00:00Z" link: http://classicprogrammerpaintings.tumblr.com title: Classic Programmer Paintings --- > Painters and Hackers: nothing in common whatsoever, but this are classical painters depictions of software engineering (techincally, might not be all classical but hey, this is just a tumblr) The curators that created [this Tumblr](http://classicprogrammerpaintings.tumblr.com) are genius. Here is one: "Frontend programmer" - a 16th century painting by Giovanni Battista Moroni. ![](/img/frontend-programmer.jpg) Or this one: “Sysadmin grants sudo privileges to developer on production web server” - Andrea del Verrocchio and Leonardo da Vinci ![](/img/sudo.jpg) --- --- date: "2016-04-14T00:00:00Z" link: http://googleasiapacific.blogspot.sg/2016/02/building-engineering-team-in-singapore.html title: Google kick-starts engineering team in Singapore --- Great news indeed! Now one can work at one of the most prestigious company in the world and stay close to his family. Singapore is only less than 2 hours away from Vietnam. You can view all opening positions [here](https://www.google.com/about/careers/locations/sing/) --- --- date: "2016-04-13T00:00:00Z" link: https://plus.google.com/+DouglasCrockfordEsq/posts/RK8qyGVaGSr title: Why JSON doesn't support comments --- > "I removed comments from JSON because I saw people were using them to hold parsing directives, a practice which would have destroyed interoperability. I know that the lack of comments makes some people sad, but it shouldn't. Suppose you are using JSON to keep configuration files, which you would like to annotate. Go ahead and insert all the comments you like. Then pipe it through JSMin before handing it to your JSON parser." - **Douglas Crockford** --- --- date: "2016-03-28T00:00:00Z" link: http://oldblog.antirez.com/post/autocomplete-with-redis.html tags: - Redis title: Implement autocomplete with redis --- Very nice implementation of doing autocomplete with redis. From antirez - redis author himself. I tried it in Node.js and it works very well for this purpose, over 10x improvement over traditional SQL `LIKE`. ![redis autocomplete](/img/redis-autocomplete.png) --- --- date: "2016-03-28T00:00:00Z" link: http://concur.rspace.googlecode.com/hg/talk/concur.html title: Concurrency and parallelism --- My interest shifted to Go lang these days. However, I didn't want to jump right into coding but reading about its design first. For example, [this slide here](http://concur.rspace.googlecode.com/hg/talk/concur.html) is pretty fascinating on how golang deals with concurrency and parallelism, a concept I believe many people often make mistake and confuse between them. --- --- date: "2016-03-24T00:00:00Z" title: Welcome the second Pom to my family --- 47 days old. She's the latest furry cute little addition to my family. *We haven't named her yet though.*
--- --- date: "2016-03-23T00:00:00Z" title: Setting up SSL certificate with Deis thumbnail: "/img/ssl-cert.jpg" --- Now that Let's Encrypt offers free SSL, there is no reason not to use it to secure your apps. Setting up [Deis to use SSL is easy too](http://docs.deis.io/en/latest/using_deis/app-ssl/). I haven't yet figured it out how to renew the cert automatically. Last I checked, they (Deis) doesn't support this yet. They may be [integrated into deis-router in near future](https://github.com/deis/deis/issues/4681). --- --- date: "2016-03-21T00:00:00Z" title: Search airport by city name --- I recently came across a case where I have an airport list (my data), city list (input data) and I have to map them. The thing is not all the airport and city have similar name. They can be very different like `Vieux Fort` city and `Hewanorra International Airport (UVF)`. I need to come up with a small service that take city name as input and output their possible nearby airports. What I did was - I use [Google Maps Reverse Geocoding API](https://developers.google.com/maps/documentation/geocoding/intro?csw=1#Geocoding) to find the city's longitude and latitude and cache it. Since Google Maps has a limit of 10 requests per second and 2,500 request daily. Without caching it, my app would crash within seconds. I used redis here. - Since this is one time thing, I will try to use the free tier of Google Maps API if possible without having to pay. I might take a look at using proxy for removing that free tier cap. - I will have to find distance between that input city and all the airports. Doing this would be super slow so you would want to consider all the city in that country only. Few things to note here: - redis is awesome cache solution. Since I already use redis for caching, and it can do [geo proximity calculation](http://redis.io/commands/geoadd), I wonder why not. - Google Maps API is not very reliable. Occasionally, you will see many dumb data which played havoc to my service. Hence I'm only using this as last resort if I can't map using other method. --- --- date: "2016-03-03T00:00:00Z" title: How to install Discourse without Docker --- I purchased a dead cheap VPS for 5$ a month which come with 2GB of memory, 80GB disk space and quite decent CPU. The downside is they uses a very old kernel (`2.6.32-042stab108.8` to be specific) while the official Discourse installation guide use Docker which require a more recent one (3.x). Docker sure makes it easy to install Discourse but it's the only reason that newer kernel is required. After a quick research, I found this [Ansible playbook](https://github.com/jamielinux/ansible-discourse). It looks well-maintained (last commit was Dec 2015). It doesn't work right out of the box for me but after some debugging, I was able to install Discourse just fine. There are few things to note though. * On default Ubuntu installation, Apache will use port 80, making nginx fails to start. You will have to stop/uninstall Apache2 in advance (`sudo apt-get remove apache2`) * On some VPS providers, IPv6 is not enabled so you will also need to tweak nginx configuration to bind to IPv4 only. * If `locale` is not set, the script will fail as well. This command `localedef -v -c -i en_US -f UTF-8 en_US.UTF-8` fixes it. ## Installing plugins `discourse` is the default username if you use the above playbook. Replace it if needed. cd /var/www/discouse/plugins # git clone the plugin you want to install sudo /home/discourse/.rbenv/shims/bundle exec rake db:migrate /var/www/discourse RAILS_ENV=production sudo /home/discourse/.rbenv/shims/bundle exec rake assets:precompile /var/www/discourse RAILS_ENV=production sudo systemctl reload discourse-unicorn.service Bonus: btw, Let's Encrypt offers free SSL. Setting it up is really easy too. --- --- date: "2016-02-19T00:00:00Z" title: A look back at 2015 --- I kicked off 2015 with lots of anxiety. I reconnect with my best friend from secondary school (then; wife now). For the first time in years, I really wanted to move back to Vietnam. Early in 2015, I went back to Thailand after Tet holiday, start looking around for a job in Vietnam or any job that accept remote workers to smooth the transition. I found one later in May-June. They only require me to work in Thailand for awhile (1 month) so that I can get accustom with how they work there.
me and my wife
Got married to a beautiful girl who also happens to be my best friend from secondary school.
me and my wife
Bought a beautiful Pomeranian. Named her Min.
my dog
My family and me during Tet 2016.
Grow a little garden on the balcony
2015 had been good to me and perhaps my most important year of my life. I'm looking forward to 2016 with many goals in mind. The difference is now I got my wife to back me up 👫. --- --- date: "2016-01-21T00:00:00Z" link: http://techcrunch.com/2016/01/21/mariadb-raises-9m-more-michael-howard-named-new-ceo-monty-widenius-cto/ title: MariaDB Raises $9M More, Michael Howard Named New CEO, Monty Widenius CTO --- > Michael “Monty” Widenius, the man who originally created MySQL and MariaDB, is now joining the startup as CTO. This is better than the funding round news. --- --- date: "2016-01-20T00:00:00Z" title: Chuyện tiền nong --- ![](/img/tien-nong.jpg) ngồi lẩm nhẩm: mình đi làm mấy năm, nhìn lại thấy tài chính rối tung, tài sản thì chả có gì; cơ hội thì bỏ qua 1 đống. thôi thì bây giờ học cũng chưa quá muộn. ## tránh mang nợ nói chung có thì xài, không có thì đừng có vay nợ để xài. xài thẻ debit cũng có nhiều cái hay lắm. hết tiền rồi mà muốn mua món gì đó thì phải ra ngân hàng deposit hoặc chuyển khoản từ ngân hàng khác sang. rất mất thời gian. cái này là vật cản lớn lắm nha. nhiều lúc mình tính mua món gì mà ngại chuyển tiền. tính ngủ 1 giấc rồi đi mua thì lại chẳng còn hứng mua xài tiền nữa. nếu mà như vậy thì thực sự bạn không cần món đó, chỉ là muốn thôi. ## lỡ mang nợ rồi!? bây giờ lỡ mang nợ rồi thì sao? có 2 cách trả nợ: 1. trả cái nào lãi suất cao nhất trước. cách này là economical nhất vì số tiền phải trả cuối cùng là nhỏ nhất. 2. trả cái nào nhỏ nhất. cái này là dựa về mặt tâm lý. dứt được 1 món nợ sẽ thoải mái tinh thần hơn nhiều và tạo thêm động lực. 1 dạng reward cá nhân. ## kiếm đâu ra tiền để trả nợ? cái này cũng có 2 cách: 1. cày nhiều hơn. 2. xài ít hơn. dù có chọn cách nào thì cũng phải xài tiền khôn hơn 1 chút. xài ngu thì kiếm bao nhiêu cũng không đủ. lương tháng nào xài tháng đó hoặc còn xài lố (credit card) nữa thì thua. ## làm giàu cách đơn giản và an toàn nhất là bỏ vào 1 cuốn sổ tiết kiệm. giả dụ mỗi ngày bạn bỏ ra 100 nghìn, 10 năm sau bạn có 100k * 365 * 10 = 365 triệu. 30 năm sau là thành tỉ phú rồi. một cách khác nữa là đầu tư vào cái gì đó. cái này mình cực ngu. một là do không có vốn (vì lỡ xài ngu như ở trên rồi), hai là không nhìn thấy cơ hội. cách khác nữa là đầu tư vào bản thân. mua sách học, đăng kí các khóa học thêm, etc.. --- hehe nói vầy thui chứ mình không phải chuyên gia tài chính gì đâu, các bạn đừng có tin. nếu làm được mấy cái này mình đã giàu rồi, ko phải ngồi đây viết lảm nhảm như vầy đâu haha. --- --- date: "2016-01-15T00:00:00Z" title: Better MySQL pagination --- Consider this ``` SELECT * from Bookings LIMIT 5000,10 ``` versus this ``` SELECT * from Bookings INNER JOIN (Select id FROM BOOKING LIMIT 5000,10) AS result USING (id) ``` My `Bookings` table has merely 6000 records yet the first query takes approx ~10 seconds which is outrageous. Luckily, we can optimize this using late lookups like in query 2. In the second query, we select `id` from `Bookings` and then join the original table back. This will make each individual row lookup less efficient but the total number of lookups will be reduced by a lot. Also, if you could pass more condition into the select, it will greatly improve the performance as well. So instead of making your paging url like this `example.com/products?page=10`, you can use `example.com/products?page=10&last_seen=1023`. From that, you can pass `WHERE id > 1023` into the pagination query making the whole thing a lot faster. Those are what I used for optimizing pagination. If you know any other, please let me know. Peace out, everybody. --- --- date: "2016-01-06T00:00:00Z" link: https://cr.yp.to/qmail.html title: Qmail --- > qmail is a mail transfer agent (MTA) that runs on Unix. It was written, starting December 1995, by Daniel J. Bernstein (djb) as a more secure replacement for the popular Sendmail program. Một chút background: vào khoảng những năm 80,90 của thế kỷ trước, các máy chủ email và dns thường sử dụng Sendmail và BIND9. Tuy nhiên, 2 phần mềm này có [cực kì nhiều lỗi](https://en.wikipedia.org/wiki/Sendmail#Security). djb viết ra qmail và djbdns để thay thế 2 phần mềm này và tuyên bố 2 phần mềm này là "bug-free". Hai phần mềm này được chạy trên hàng triệu tên miền và cực kì ổn định. Lỗi đầu tiên của qmail được tìm thấy sau gần 1 thập kỉ đủ để nói lên sự ổn định của nó. Có lẽ trong lịch sử chưa có một lập trình viên nào đạt đến ngưỡng này. > Daniel Julius Bernstein (sometimes known simply as djb; born October 29, 1971) is a German-American mathematician, cryptologist, programmer, and professor of mathematics and computer science at the Eindhoven University of Technology and research professor at the University of Illinois at Chicago. He is the author of the computer software programs qmail, publicfile, and djbdns. --- --- date: "2016-01-06T00:00:00Z" link: http://hueniverse.com/2014/08/20/performance-at-rest/ title: Performance at Rest --- TL;DR > Benchmarking frameworks is fucking stupid. --- --- date: "2015-12-19T00:00:00Z" tags: - Programming title: Explicit over clever --- I always prefer explicit over clever, hacky hack. Explicit make the code looks clearer, more maintainable and leaning toward a more predictable behavior (aka junior developers will be less likely to mess it up). Take an example of this code where I have a folder called `providers`. This here below is the content of the `index.js` file which basically read all the files in that folder (except `index.js`), require them and then `module.exports` that. It seems simple enough right? ``` var fs = require('fs'); var path = require('path'); var basename = path.basename(module.filename); var providers = {}; var extension = '.js'; fs .readdirSync(__dirname) .filter(function(file) { return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === extension); }) .forEach(function(file) { var provider = require(path.join(__dirname, file)); var key = makeKey(file,extension) providers[key] = provider; }); /* ... */ ``` Now, take a look at this code where I explicitly require all the files in that folder (sounds exhausting right?) ``` let providers = { Auth: require('./auth'), User: require('./user'), Health: require('./health') } /* ... */ module.exports = providers ``` Now, say if you were a newly joined member of the project, which looks less intimidating to you? I'm not saying we can't achieve both explicit and clever at the same time; but if I need to make a choice selecting one over another, I would always go with explicit. --- --- date: "2015-12-14T00:00:00Z" title: Step by step how to install Deis on AWS --- > Deis (pronounced DAY-iss) is an open source PaaS that makes it easy to deploy and manage applications on your own servers. Deis builds upon Docker and CoreOS to provide a lightweight PaaS with a Heroku-inspired workflow. I struggled installing [Deis](http://deis.io/overview/) and it took me several times to get it right. Deis's documentation is correct but not very straight forward so I decided to write this to help others that struggle like me. This steps works for me as of version `1.12.2` ## Preparation * Install [deisctl](http://docs.deis.io/en/latest/installing_deis/install-deisctl/#install-deisctl). This is needed for provision script. ``` $ cd ~/bin $ curl -sSL http://deis.io/deisctl/install.sh | sh -s $ # on CoreOS, add "sudo" to install to /opt/bin/deisctl $ curl -sSL http://deis.io/deisctl/install.sh | sudo sh -s ``` * Install AWS Command line interface and configure it ``` $ pip install awscli $ pip install pyyaml $ aws configure AWS Access Key ID [None]: *************** AWS Secret Access Key [None]: ************************ Default region name [None]: us-west-1 Default output format [None]: ``` * Generate and upload keys to AWS. Also add it to `ssh-agent` so that it can use during provisioning the cluster. ``` $ ssh-keygen -q -t rsa -f ~/.ssh/deis -N '' -C deis $ aws ec2 import-key-pair --key-name deis --public-key-material file://~/.ssh/deis.pub $ eval `ssh-agent -s` $ ssh-add ~/.ssh/deis ``` * If you want to use more than 3 instances (default), just export `DEIS_NUM_INSTANCES` ``` $ export DEIS_NUM_INSTANCES=5 ``` ## Provision the cluster * Clone the repo, git checkout the latest tag. At repo root, run this command below to create discovery url. Forget to do this will result in etcd not configured properly. ``` $ make discovery-url ``` * Next, go to folder `contrib/aws/` in deis repo, create a file name `cloudformation.json` in order to override default values. You can take a look at the template file `deis.template.json`. * Run the provision script ``` $ cd contrib/aws $ ./provision-aws-cluster.sh Creating CloudFormation stack deis { "StackId": "arn:aws:cloudformation:us-east-1:69326027886:stack/deis/1e9916b0-d7ea-11e4-a0be-50d2020578e0" } Waiting for instances to be created... Waiting for instances to be created... CREATE_IN_PROGRESS Waiting for instances to pass initial health checks... Waiting for instances to pass initial health checks... Waiting for instances to pass initial health checks... Instances are available: i-5c3c91aa 203.0.113.91 m3.large us-east-1a running i-403c91b6 203.0.113.20 m3.large us-east-1a running i-e36fc6ee 203.0.113.31 m3.large us-east-1b running Using ELB deis-DeisWebE-17PGCR3KPJC54 at deis-DeisWebE-17PGCR3KPJC54-1499385382.us-east-1.elb.amazonaws.com Your Deis cluster has been successfully deployed to AWS CloudFormation and is started. Please continue to follow the instructions in the documentation. ``` ## Install platform ``` $ export DEISCTL_TUNNEL= $ deisctl config platform set sshPrivateKey=~/.ssh/deis $ deisctl config platform set domain=deis.example.com # create a CNAME point this to the load balancer $ deisctl install platform $ deisctl start platform ``` After this, you should have a proper configured Deis cluster. Just install the client, register an account and you should be ready to deploy your very first application on Deis. --- --- date: "2015-11-14T00:00:00Z" link: http://www.metalsmith.io title: Metalsmith - a static site generator written in Node.js --- > An extremely simple, pluggable static site generator. Metalsmith works in three simple steps: * Read all the files in a source directory. * Invoke a series of plugins that manipulate the files. * Write the results to a destination directory! Very simple yet powerful. Metalsmith is like `gulp` but made for static website/content. --- --- date: "2015-11-07T00:00:00Z" link: https://github.com/gbrueckner/Snapp title: Snapp --- > Drag a window up to the menubar to maximize it, or drag a window to the side to resize it to the corresponding screen half. [Demo](https://gfycat.com/ifr/AbsoluteHairyCrossbill) --- --- date: "2015-11-07T00:00:00Z" excerpt: Easiest way to setup rtorrent/rutorrent web ui with Docker title: Possibly the easiest way to setup rtorrent/rutorrent --- Installation script which is cumbersome, not very easy to use and sometimes goes unmaintained but you don't know about that yet and ended messing up your VPS file system. In this post, I will show you how to setup rtorrent/rutorrent with Docker. It's rather easy and requires minimal knowledge about Linux in general. ## Grab a KVM VPS from RamNode I'm going with [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728) here but you can use any VPS provider you want. Be sure to select KVM because Docker is going to require a rather new kernel so OpenVZ won't work. I select Ubuntu 14.04 in this tutorial. To make it easy to follow, you can do the same. After finishing the payment, you will get an email containing your VPS information. You can perform some simple initial server setup to secure your VPS after this. ## Install Docker ~~~ sudo apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D sudo touch /etc/apt/sources.list.d/docker.list sudo echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" > /etc/apt/sources.list.d/docker.list sudo apt-get update sudo apt-get install docker-engine mkdir ~/rtorrent # folder for rtorrent docker sudo usermod -aG docker # use docker without sudo, if your user is in sudo group ~~~ ## Setup rtorrent/rutorrent You will need to secure your `rutorrent` installation so that noone else except you can access it. ~~~ cd ~/rtorrent printf ":$(openssl passwd -apr1 "")\n" > .htpasswd ~~~ Now we are going to use the Dockerfile made by diameter to simplify the whole process of setting up rtorrent/rutorrent/nginx. If you want to see the source code, you can check it out [here on Docker Hub](https://hub.docker.com/r/diameter/rtorrent-rutorrent/). ~~~ docker run -dt --name rtorrent-rutorrent -p 8080:80 -p 49160:49160/udp -p 49161:49161 -v ~/rtorrent:/downloads diameter/rtorrent-rutorrent:64 ~~~ Let `docker` pull and build for awhile (1,2 mins at most) and you can navigate to `http://:8080` to start using `rutorrent`. ![rutorrent docker](/img/rutorrent-docker.png) --- --- date: "2015-10-20T00:00:00Z" title: Stuff you may not know about console --- ## console.table() var Person = function(name, age) { this.name = name this.age = age } var person_list = [ new Person('wayne', 10), new Person('cristiano', 20), new Person('david', 30), new Person('bastian', 40) ] console.table(person_list, ['name', 'age']) // filtering columns console.table(person_list) You'll get a nice table like this ![console.table](/img/console-table.png) ## console.time() console.time('Operation A') var arr = [] for (var i =0; i < 100000; ++i) { arr.push(i) } console.timeEnd('Operation A') > Operation A: 395.557ms ## console.assert() for (var i = 0; i < 100000; ++i) { console.assert(i % 1000, 'Iteration #%d', i) } This will only print when first arg (`i % 1000`) is `false`. ## console.group() Group so that you can collapse/expand messages within a group. console.group('Group A') console.log('First line') console.log('Second line') console.log('Third line') console.groupEnd() ![](/img/console-group.png) --- --- date: "2015-10-10T00:00:00Z" description: Node.js best practices title: Node.js happy coding --- I've been doing Node.js professionally for roughly 2 years. During that time, I've learnt a thing or two that keeps me away from troubles. --- ## Use Promise instead of callback ES6 gets native Promise already but if you prefer something more convenient that 3rd-party libraries offer, pick something like `bluebird` (it's really really fast). Stay away from `Q`. I mean, just [look at this](https://github.com/petkaantonov/bluebird/tree/master/benchmark). ## Do not trust developer's semver practice use `npm shrinkwrap` instead This command locks down the versions of a package's dependencies so that you can control exactly which versions of each dependency will be used when your package is installed. Personally, i would prefer `npm` support something like `--save-exact` flag. That would be awesome. ## Choosing the right dependency You already have enough bug fixing jobs on your plate. Don't import more from others'. There are several things that go into consideration when i need to install an extra package: * statistic on npm/github: popular is good * check if the project is active: last commit, etc.. * check if the issues get resolved timely. * check unit testing is well covered? * check its dependencies: cover all things above. Personally, I wouldn't want to use anything that depends on `Q`. It shows the author didn't do the homework quite well, or at the very least, didn't keep the package up to date with current situation in Node.js eco. ## Use linter Respect the code convention. The ultimate goal is having code written by different developers, looks as if they were written by the same person. --- --- date: "2015-10-09T00:00:00Z" link: https://nodejs.org/en/docs/es6/ tags: - Node.js title: ES6 in Node.js --- Get yourself ES6-ready in Node.js: arrow functions, map, generators, etc.. --- --- date: "2015-10-01T00:00:00Z" link: http://sagivo.com/post/130207525903/nodejs-addons tags: - Node.js title: Go Native - Calling C++ From NodeJS --- I don't usually have the need to do this but it would be fun practice to build a npm module by packaging some C++ libraries. --- --- date: "2015-07-15T00:00:00Z" title: Atom is slow but that's okay --- I started using Atom from one of those early beta build. I wasn't impressed. At that time, it was really slow (still is), taking several seconds just to load the app, freeze frequently when I tried to open log file (few MBs) and a bunch of other small bugs here and there. So I went back to Sublime and just keep an eye on Atom once in awhile (*I left it in `/Applications` untouched*) Recently, a colleague of mine was trying to convince me to try Atom again. So I fire up terminal, `rm -rf ~/.atom` and update the app. *You should remove old Atom config since Atom is very actively developed and may have changed a lot since last you tried the app.* Atom feels much more polished this time. Despite the slow startup time still there, I feel very compelled to stick with Atom this time. I understand that Atom probably never going to achieve Sublime's speed (due to its nature) but that's ok. Here's why: ## Atom's UI is closely resemble Sublime The interface and everything is closely resemble Sublime so the learning curve is rather shallow. In fact, few people wouldn't know they are looking at Atom or Sublime without glancing at the menubar. ## Atom has done so many right things out of the box Atom has done so many right things right out of the box. Stuff like keyboard shortcuts feel a lot more intuitive than Sublime. *I know, i know* that we can re-map Sublime shortcuts as well. I did exactly that at first but working right out of the box still feels a lot nicer. The older I get, the lazier I become when it comes to tinkering with settings to get it right. I remember my 18-yo self would plays with all kind of registry keys on Windows XP for the fun of it. I'm not like that anymore. I just want to forget about the editor and start coding. ## Atom is very extensible Most of the packages I used in Sublime is available on Atom as well; or at the very least, the equivalent exists. I pretty much use vanilla Atom (default theme, default colorscheme) with a few packages installed: `atom-alignment`, `atom-fuzzy-grep`, `auto-detect-indentation` and `highlight-selected`. I highly recommend you to checkout `atom-fuzzy-grep` package. It allows you to use `the_silver_searcher` for fuzzy search your project. It's freaking awesome and fast. Check it out. ## Atom is actively developed by GitHub and community It's not that Sublime is abandoned but Atom development is much more active. It's backed by GitHub and a huge open-source community vs few developers of Sublime. You know, the bus factors thing. --- If you haven't checked Atom in while (or at all), you should probably do. That's my advice, coming from a hardcore Sublime fan. --- --- date: "2015-07-09T00:00:00Z" link: https://github.com/nvie/gitflow title: Efficient branching workflow with git flow --- [Why aren't you using git flow](http://jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/) provides a nice overview on why you and your team should adapt using `git flow` in your workflow. Keep this [cheatsheet](http://danielkummer.github.io/git-flow-cheatsheet/) somewhere in case you need a quick reference. --- --- date: "2015-07-04T00:00:00Z" title: Hello, HotelQuickly! thumbnail: "/img/hello-hotelquickly.png" --- I'm very excited to let you know that I'm joining HotelQuickly as Node.js developer, working on their next generation API. I'm totally psyched about this as I have a chance to work with great people, dealing with mind-boggling challenges. This is going to be fun. Meanwhile, you can [download HotelQuickly now](https://www.hotelquickly.com/invite/TANHB9/) for iPhone, Android or BlackBerry 10; get 500 THB credit by redeeming voucher code **TANHB9**. --- --- date: "2015-06-26T00:00:00Z" title: Cancelling $http request in AngularJS --- It's as simple as this. Basically you return a promise (`canceller`) and let those who call your service ability to cancel via `canceller.resolve('reason goes here')`. Alternatively, you can provide a function `cancel` which does the same as `canceller.resolve()` if you don't want to expose the `canceller` promise. ```js .service('MyService', function($http, $q) { this.canceller = $q.defer() this.my_func = function(params) { params.timeout = canceller.promise return { promise: $http.get(url, params), canceller: canceller } } }) ``` To verify if your code is working as expected, you can check in `Network` tab in Developer Tools. The request will be marked with status as `cancelled`. --- --- date: "2015-06-16T00:00:00Z" title: How to install ZeroMQ on Ubuntu --- Before installing, make sure you have installed all the needed packages ~~~ sudo apt-get install libtool pkg-config build-essential autoconf automake sudo apt-get install libzmq-dev ~~~ ## Install libsodium ~~~ git clone git://github.com/jedisct1/libsodium.git cd libsodium ./autogen.sh ./configure && make check sudo make install sudo ldconfig ~~~ ## Install zeromq ~~~ # latest version as of this post is 4.1.2 wget http://download.zeromq.org/zeromq-4.1.2.tar.gz tar -xvf zeromq-4.1.2.tar.gz cd zeromq-4.1.2 ./autogen.sh ./configure && make check sudo make install sudo ldconfig ~~~ ## Verify to see if zeromq installed correctly I'm familiar with Node.js so I'm going to use the Node.js `hwserver` [example](http://zguide.zeromq.org/js:hwserver) from zeromq's website. If you see "Listening on 5555…", you're good to go. ~~~ // Hello World server // Binds REP socket to tcp://*:5555 // Expects "Hello" from client, replies with "world" var zmq = require('zmq'); // socket to talk to clients var responder = zmq.socket('rep'); responder.on('message', function(request) { console.log("Received request: [", request.toString(), "]"); // do some 'work' setTimeout(function() { // send reply back to client. responder.send("World"); }, 1000); }); responder.bind('tcp://*:5555', function(err) { if (err) { console.log(err); } else { console.log("Listening on 5555…"); } }); process.on('SIGINT', function() { responder.close(); }); ~~~ --- --- date: "2015-06-15T00:00:00Z" title: My first Hackathon thumbnail: "/img/angelhackbkk4.jpg" --- So I went to my first hackathon this weekend and it was fun. There are plenty of pizza, soda, beer for everyone. Chances to meet up with many awesome, cool people from various places. ![AngelHackBKK](/img/angelhackbkk4.jpg) The way I think of hackathons is like: a contest to see which team can come up with the coolest idea and quickly create a working prototype for it by glueing libraries and datasets together. Well, it's exactly like that. ## Hackathons is exhausting The suffix `-athons` pretty much speaks for itself. One whole weekend with intensive work, lack of sleep and lots of caffein/red bulls to keep you awake. Some may argue that it's a hackathon, it's an occasional event to let people push themselves. Even I'm competitive but I'm definitely not the type of guy that can pull up 48 hours straight no-sleep to work on something. I'm just not that guy. ![AngelHackBKK](/img/angelhackbkk1.jpg) ![AngelHackBKK](/img/angelhackbkk2.jpg) ![AngelHackBKK](/img/angelhackbkk3.jpg) ## Hackathon projects are far from usable Stuff created at Hackathons are far from usable and usually don't survive pass the hackathon weekend. **Very** fews manage to emerge into something bigger. In order to do that, they require a substantial amount of time and development efforts after the hackathons. Projects from hackathons are not really answers for existing problems. I mean like, how can we do that by showing up in the morning, hear someone pitching idea, jump in and design a solution without any prior research. Hard problems exist for a reason; it's because they are hard. ## What hackathons are good for? However, I definitely enjoy it less of a competition and more of a social event. I get to grab a drink, talk with a bunch of like-minded people. Also, it feel good to be able to come up with a working prototype of some new cool idea we have. Working with a team you ain't familiar with. I have to execercise different parts of my skillset which I don't normally use at work; working with different stack/domain; quickly patch here and there to be able to catch the code freeze timeline. --- In short: hackathons can be fun; they inspire us working on new ideas, pushing our own boundaries but it shouldn't be a regular thing. Also, think of more its social aspect rather than a competition. It's more fun that way. *P.S.: Our team got second prize. Maybe next time.* --- --- date: "2015-06-12T00:00:00Z" title: Vì sao tôi thích xài email hơn là điện thoại --- ## Phone call leaves no trace Tôi cũng không rõ cái này là tốt hay xấu nữa nhưng theo kinh nghiệm bản thân thì xấu nhiều hơn tốt. Tôi hay quên nên đã từng gặp khá nhiều trường hợp cãi nhau vì bạn B quả quyết đã nói/bàn vấn đề đó qua điện thoại. Bởi vậy sau này tự tập cho bản thân thói quen sau khi bàn bạc gì đó qua phone thì sẽ viết 1 email follow-up để tóm tắt lại những ý đã bàn qua cuộc gọi vừa rồi. Hơi rắc rối hơn phải ko? Thay vì vậy thì cứ bắt đầu từ email luôn cho rồi. ## Phone call is awkward Gọi điện thoại cho ai đó cũng như kiểu tới nhà người khác và bấm chuông mà ko hẹn trước vậy. Ví dụ này xem ra hơi quá nhưng đại ý là vầy. Nếu không thực sự cần thiết và không quá gấp thì tôi thấy ko nên gọi. Một vấn đề nữa là nhiều khi bạn cũng không biết được người nhận cuộc gọi đang làm gì, có tiện nói chuyện hay không nữa. Bài học là: Nếu thực sự cần gọi thì nên hẹn trước thời gian. ## Email is a better communication channel Tôi diễn đạt kém. Bởi vậy tôi thấy sử dụng email giúp tôi thể hiện ý tôi muốn 1 cách tốt hơn. Tôi có thể đọc đi đọc lại nhiều lần, chỉnh sửa cho tới khi thấy ngắn ngon và súc tích. Gọi điện thoại thì ko được như vậy. --- Cũng vì các lý do trên mà tôi ít sử dụng Phone.app hơn và thay vào đó là email app trên Dock. Tôi cũng chẳng nhớ mình bỏ Phone.app ra ngoài từ khi nào. Có lẽ là cũng khá lâu rồi và tôi không hề cảm thấy bất tiện. --- --- date: "2015-06-11T00:00:00Z" tags: - Jekyll title: Markdown table sucks. Let's use jekyll's data files instead --- Creating a table in markdown sucks, especially if the table is quite big with some styled elements. Back before knowing data files, I came up with a plugin to help with this. First, I create folder to keep my `JSON` data files. I wrapped it in a simple plugin to generate inline javascript call (AJAX) to render the table upon finish loading. {% jsontable myjsonfilename %} This works as expected but it's only feasible for simple table structure. Sometimes, I want a specific table structure for a specific post. Data files are perfect for this purpose. Data files structure is simple, easy to maintain and accessible throughout your blog via `site.data.datafilename`. *There's an [example](http://jekyllrb.com/docs/datafiles/) on jekyll's documentation if you want to take a look.* --- --- date: "2015-06-10T00:00:00Z" title: Tắt tiếng của tab ở Chrome --- Ever notice the volume icon on Chrome tab and wonder why you can't click to mute it before? Well, you can now. Just copy and paste the code below to see hidden flags in Chrome, enable it and relaunch Chrome afterward. You can now click the volume icon to mute that particular tab. chrome://flags/#enable-tab-audio-muting --- --- date: "2015-06-09T00:00:00Z" link: http://thecodelesscode.com/case/1 title: The Codeless Code - Fables and Kōans for the Software Engineer --- > An illustrated collection of (sometimes violent) fables concerning the Art and Philosophy of software development, written in the spirit of Zen kōans --- --- date: "2015-06-09T00:00:00Z" link: https://picard.musicbrainz.org title: MusicBrainz Picard --- > Picard is a cross-platform music tagger written in Python. Great piece of software. Usually, I just add all music files I grab from the Internet through Picard before adding into iTunes for syncing. --- --- autobiographies: - author: Alex Ferguson genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69c29DU1JkOU9iMlU/view?usp=sharing title: 'Alex Ferguson: My Autobiography' - author: Alex Ferguson genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69UWJjbHVZSDJ1S0k/view?usp=sharing title: 'Managing My Life: My Autobiography' - author: David Beckham genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69TGJGcHZOVmI5YlU/view?usp=sharing title: 'David Beckham: My Side' - author: Eric Cantona genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69LXo3Q3AtQTNHbmM/view?usp=sharing title: 'Cantona: The Rebel Who Would Be King' - author: Gary Neville genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69cTMyZ05yV2FESGs/view?usp=sharing title: 'Red: My Autobiography' - author: Paul McGrath genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69c0ZTdkY2aXFFQkk/view?usp=sharing title: 'Back from the Brink: The Autobiography' - author: Rio Ferdinand genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69NkItVFF4LVBEQzg/view?usp=sharing title: '2sides: Rio Ferdinand - My Autobiography' - author: Roy Keane genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69NEE0a2dTRE1hVkk/view?usp=sharing title: The Second Half - author: Ryan Giggs genre: Biography link: https://drive.google.com/file/d/0B5lnD5gfVd69VkFnSzNXY2hiRUk/view?usp=sharing title: 'Giggsy: The Biography of Ryan Giggs' - author: Wayne Rooney genre: Autobiography link: https://drive.google.com/file/d/0B5lnD5gfVd69ZnNrZ3NVSzRGSVE/view?usp=sharing title: 'Wayne Rooney: My Story So Far' date: "2015-06-09T00:00:00Z" title: A collective list of Manchester United players' autobiographies --- Autobiographies/biographies of ManUtd's players that I've read so far. {% for item in page.autobiographies %} {% endfor %}
Title Type Player Link
{{item.title}} {{item.genre}} {{item.author}} Download
--- --- date: "2015-06-08T00:00:00Z" link: http://doisong.vnexpress.net/photo/chuyen-doi/bai-hoc-ve-dac-quyen-con-nguoi-qua-tro-nem-giay-vao-thung-3230453.html title: Đặc quyền con người qua trò ném giấy vào thùng thumbnail: "/img/dac-quyen-con-nguoi.jpg" --- dac quyen con nguoi qua tro nem giay > (...) when conservatives go on about "equality of opportunity, not equality of outcome," they're either blinded to the fact that equality of opportunity doesn't exist or they're aware of that fact and simply being disingenuous. Be aware of the opportunities that we enjoy, which others may not. Our success might not be merely our own, can hopefully instill in us some empathy for people who didn't have similar opportunities. --- --- date: "2015-06-07T00:00:00Z" link: http://tuoitre.vn/tin/tuoi-tre-cuoi-tuan/van-de-su-kien/20150128/ve-mot-thach-thuc-trong-qua-trinh-hoc-hoi/704502.html title: Về một thách thức trong quá trình học hỏi --- > Mỗi đất nước, dân tộc, văn hóa, thậm chí mỗi cá nhân là một vũ trụ phức tạp, chứ không phải là những nhân vật của một vở kịch tuyên truyền thô thiển: người này giỏi, người kia kém, trắng đen rõ ràng và bất biến. Thách thức trong quá trình học hỏi là nhận biết được sự phức tạp, những xung đột, những chấn thương, bế tắc, những năng lượng của một quốc gia, một nền văn hóa.
Ngược lại, càng mù quáng, tôn sùng vô điều kiện một cái gì đó thì lại càng dễ thất bại. Và khi vỡ mộng, người ta lại càng vội vã tuyên bố phải “thoát” nó ngay lập tức để chạy tới tôn thờ một cái mới. Tâm thế đó là tâm thế của những kẻ mất tự do. Nói chung là mỗi nền văn hóa đều có điểm tích cực và tiêu cực. Nên học hỏi 1 cách có lựa chọn thay vì tôn sùng mù quáng. --- --- date: "2015-03-11T00:00:00Z" title: Love --- We all want **Love**. There’s always someone for somebody. Be **vulnerable**. Break down your walls. Open up. Be kind to yourself. It will come. It will happen. Don’t try to find it, somebody’s gonna find you. Choose your relationships. Value those who are important to you. Do not take them for granted, ever. And most of all, love. **Always**. --- --- date: "2015-02-15T00:00:00Z" description: 'The best OS X app for managing SSH tunnels: local forward, remote forward and auto toggle socks proxy. Free for OS X 10.9 and up' link: http://www.opoet.com/pyro/ title: Managing multiple SSH tunnels is easy with Secure Pipes --- I know we can easily get away with a single command line but this app makes it so much easier for dealing with multiple ssh tunnel. Also, without the hassle of going to Network Preferences to toggle socks proxy. This is the best app I tried so far. [Secure Pipes - Free](http://www.opoet.com/pyro/) --- --- date: "2015-02-05T00:00:00Z" link: https://github.com/oracle/node-oracledb tags: - Oracle - Node.js title: Official Oracle driver for Node.JS --- Currently at `v0.2`. `node-oracledb` makes use of C API for performance which makes it more like a "thick client". That mean you are required to install Oracle's client lbiraries[^oracle-client-libraries]. Though versioned as `0.2`, node-oracledb already supports a lot of features such as connection pooling, statement caching, client-result caching, etc... `node-oracledb` is not yet available through `npm`. You will have to clone and run `npm install` manually by yourself. Windows support is still pretty rough but I don't actually care much. I don't have much need for Windows server these days anyway. [^oracle-client-libraries]: [Oracle client libraries](http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html) --- --- date: "2015-02-04T00:00:00Z" link: https://github.com/atom/atom/releases/tag/v0.177.0 tags: - iojs title: Atom switches to io.js --- Great news for `io.js` team. There are quite a few big names in OSS joining the io.js train already, namely NW.js, Atom. To be fair, I don't want `io.js` to merge back to nodejs if they succeed. The more I read about nodejs and Joyent, the more I hate them. io.js should just proceed on their own. As for Atom, it still fails to impress me after a long time. Loading application alone (no project) is approx~ 2 seconds for me[^1] which is unacceptable. Code editor should be fast. If they can't even sort this out, I don't think I would bother trying their regex search or anything else performance-required. [^1]: I'm using a Macbook Air mid-2011. --- --- date: "2015-01-31T00:00:00Z" description: Incremental regeneration is now available in jekyll. Build speed is faster than before. tags: - Jekyll title: Incremental regeneration in latest jekyll build --- `jekyll` is an awesome static generator but its regenetion time is totally unexceptable. It works well when you have small number of posts but start to degrade as number of posts increases. Actually, I don't care about it much when I `git push` to the repo because my VPS is using SSD-cached but previewing the site at `localhost` is such a pain. So when I take a look at `jekyll` again today and see this [pull request](https://github.com/jekyll/jekyll/pull/3116), I'm so excited and couldn't help but clone the repo and rebuild `jekyll` to test it out myself. git clone https://github.com/jekyll/jekyll.git gem build jekyll.gemspec gem install jekyll*.gem A quick look at `jekyll` reveals that it now has a file named `.jekyll-metadata` to keep track of `mtime` of the change file and their dependencies. D:/Source/myawesomeblog.com/_posts/2015-01-31-welcome-to-jekyll.markdown: mtime: 2015-01-31 12:31:57.000000000 +07:00 deps: - /Source/myawesomeblog.com/_layouts/post.html - D:/Source/myawesomeblog.com/_includes/head.html - D:/Source/myawesomeblog.com/_includes/header.html - D:/Source/myawesomeblog.com/_includes/footer.html - /Source/myawesomeblog.com/_layouts/default.html I tested the beta gem on an old PC of mine with 5400rpm HDD to see how much speedup the new build offers. Prior this, when using `jekyll serve`, the regeneration speed is really slow. It takes minutes (literally minuteS) for a site with barely over 100 posts. ## Result It does actually speedup. A quick change in a single file now takes 17 to 40 seconds to regenerate (of total ~100 posts). Still no where near the speed I expect but it's a big step up from where it was. I'll take it :) This pull request is expected to be in `jekyll` 2.6.0 release. --- --- date: "2015-01-31T00:00:00Z" description: How to setup BitTorrent Sync as an alternative backup solution along side with Dropbox (or whatever you're using to backup) title: BitTorrent Sync as an alternate backup solution --- I've heard so many good feedbacks about BitTorrent Sync. The only complain seems to be how it isn't open-source. Today, I've decided give BitTorrent Sync (btsync) a whirl to see what all the hypes are about. The way it works is you first select a folder you want to share. btsync will create two keys: one for readonly access and the other one for read/write access. You then pass on the key to the person you want to share the file with. They paste it to their btsync client and that's all, btsync will do the rest. Here I thought why don't I use it to backup stuff to my VPS. My Dropbox is just a bit over 70GB and the 50GB free space promo is going to expire soon. I'm going to need to offload some stuff elsewhere, namely Camera Uploads. BitTorrent Sync seems to the perfect fit for this kind of thing. Plus, I have quite a lot of free space on my VPS. ## Setup So I installed btsync on my VPS. It's as simple as download the file and extract. You will get an executable `btsync`. # generate a sample config ./btsync --dump-sample-config >> btsync.conf ./btsync --config btsync.conf `btsync` by default will start on port `8888`. I then create an Apache2's virtualhost, bind it to port 8888 in order to access its webui. From my local machine, I add a folder to btsync, copy its readonly access key and add it to my server's btsync webui. Tick `Override any changed files` on the server. ## Thought BitTorrent Sync doesn't support document revision yet so Dropbox is still far superior. But the sync speed it offers is just amazing, leaving Dropbox in the dust. Though I don't trust my server keeping my data safe, it doesn't hurt to have one more alternative backup. You definitely shouldn't use it as your sole backup solution. --- --- date: "2015-01-30T00:00:00Z" thumbnail: /img/apache_mod_autoindex.jpg title: Theming Apache's mod_autoindex directory listing --- By default, your shared folder with `mod_autoindex` enabled looks like this. apache fancy index The icons looks like they were from Windows 3.1 era. I am by no mean a designer but looking at this seriously make me want to vomit. I decided to take a quick look at `mod_autoindex`'s [documentation](http://httpd.apache.org/docs/2.2/mod/mod_autoindex.html) and grab some free icons from Dribbble to make it better. Turns out, it's easier than I thought. Theming is supported out-of-the-box with `mod_autoindex`. I just have to copy some icons (frequently used file types) and add a few lines in my Apache's vhost config. I can even inject CSS too. Every element already has proper id and class so I can just inspect the page and customize to my liking. IndexIgnore /_theme # hide _theme folder AddIcon /_theme/icons/blank.png ^^BLANKICON^^ AddIcon /_theme/icons/folder.png ^^DIRECTORY^^ AddIcon /_theme/icons/upper_level_directory_icon.png .. AddIcon /_theme/icons/image.png .png .jpeg .gif .jpg DefaultIcon /_theme/icons/fallback_icon.png IndexStyleSheet "/_theme/style.css" # injecting CSS HeaderName /_theme/header.html # customize header ReadmeName /_theme/footer.html # footer Here's how mine looks like after. apache mod autoindex *If you're too lazy for this stuff and just want a decent looking theme, take a look at [Apaxy](https://github.com/AdamWhitcroft/Apaxy). Follow the installation instruction there*. --- --- date: "2015-01-29T00:00:00Z" tags: - Programming - Node.js title: Some of the most useful tips I learn when working with NodeJS --- ## modules management with npm Nodejs comes with an amazing package manager called `npm`. Start a project with `npm init` which will then create a configuration file named `package.json`, keeping track of all the modules your project is using. You don't have to manually manage this file yourself. If you want to add a package to `package.json` you can add `--save` parameter when installing it. npm install koa-static --save Also, you should ignore `npm_modules` folder when `git push` because whoever clone the repo can do `npm install` by themselves. `npm_modules` folder can grow pretty big so no one would want to `git clone` the whole thing. ## pm2 instead of forever When you start learning about NodeJS, you may notice that node process may exit unexpectedly when errors are not handling properly. [forever](https://www.npmjs.com/package/forever) is a node package that ensure node process will run continuously in the background. But forever is very limited. It doesn't have support for [clustering](https://tuananh.org/2015/01/15/scaling-nodejs-application/), very limited logging and monitoring. I later found out a much leaner solution called `pm2`. Compare to `forever`, `pm2` looks like a full solution for deployment with builtin clustering support, terminal configuration and better logging support. In fact, ever since, I only use `pm2` for my production server. Enabling clustering with `pm2` is as easy as pm2 start app.js -i 0 Using `0` means that `pm2` will utilize number of threads equals to number of your CPU's cores. You can specify the no. of child process as you want, ideally one per processor core. As for local development, I prefer [nodemon](https://github.com/remy/nodemon) to keep track of changes in my application and automatically restart the server. ## Fuck callbacks For starters, callbacks are [nightmare](https://tuananh.org/2015/01/18/fuck-callbacks-lets-use-generators/). Many popular frameworks still make use of callback heavily which creating the sense for newbies that it is the correct way of doing things in `nodejs`. It's not. Over the last 2 months, I've started using `Q`, async, bluebird and then generators. Of those, generator seems to be the most elegant solution, producing much more readable code than callbacks. ## Debugging with node-inspector I'm pretty sure most nodejs starters will use `console.log()` everywhere to debug the application. I did that. It was ok for quick debugging but sometimes, you need a little more than just `console.log`. [node-inspector](https://github.com/node-inspector/node-inspector) is a node package based on Blink Developer Tools that let you debug right in your favorite browser. Install `node-inspector` and use `node-debug app.js` for debugging. npm install node-inspector -g node-debug app.js ## The end These are some of the things that I've learnt in the last 2 months working with nodejs. Nodejs is an amazing platform but it can be a pain sometimes. These tips save me a lot of times and make developing in nodejs so much more bearable. --- --- date: "2015-01-28T00:00:00Z" description: How to install iojs on Mac OS X with nvm tags: - Node.js title: Install io.js on Mac OS X --- I believe `iojs` is not going to be a flop. Look at the current development rate, I would say `iojs` would replace `nodejs` eventually if Joyent not doing anything to turn it around. I prefer to install `iojs` with `nvm` instead of the default installer package on iojs's homepage since it allows me to switch between node version with a single command. As of current, `iojs` is basically a dropin replacement for `nodejs` so you don't have to worry much. ## Install iojs Install `nvm` - a node version manager with `homebrew` curl https://raw.githubusercontent.com/creationix/nvm/v0.23.2/install.sh | bash Install `iojs` with `nvm` nvm install iojs Add these lines to your `.zshrc` or `.bashrc` export NVM_DIR=~/.nvm source $(brew --prefix nvm)/nvm.sh% Set `iosjs` as the default node version nvm alias default iojs You can check again with `which node` to see which version of node is using. --- --- date: "2015-01-27T00:00:00Z" tags: - Programming title: Things software developers wish they had known in their 20s --- 1. The era in which a common career trajectory is to become a lifer at some software company and work one's way up to higher and higher internal positions has been over for a long time (This era had already been over in the 1990s when I was a young programmer). 2. You don't owe the company you work for anything beyond what you put your name on when you signed the employee agreement. 3. Don't stick around too long if the place you are working for isn't doing much for your bottom line, is not your dream job, and isn't adding anything new to your resume; that is, the easiest way to get a promotion and a substantial raise is to switch jobs. Don't switch jobs if you like what you are doing but also don't stay somewhere only out of loyalty to a company. 4. If you think 2. and 3. are cynical and if looking at things this way makes you feel like an asshole, console yourself with the knowledge that the company you work for views you in exactly the same way and will act on such a view in a second if it is in its interest to do so. 5. You can usually skip all hands meetings even if they are supposedly mandatory. 6. Keep an eye on corporate politics. Being a successful computer programmer is about social engineering as much as it is about software engineering. 7. Make sure you are at the meetings at which the scope and/or requirements are defined for any piece of software for which you are going to be responsible. 8. Don't go dark. Check in code often -- with stubs or whatever if necessary. It is better to check in code sooner rather than later despite the fact that doing so might entail dealing with transitory problems. A few hiccups now are better than a gigantic merge conflict down the line. 9. Be careful about your estimates on dates and on how long things are going to take. Understand that the whole game is about dates. When in doubt just triple what you actually think even if it secretly sounds absurd to you. 10. Don't be religious about languages, libraries, or platforms. 11. Except for Perl. Perl sucks. 12. At a certain point it is you the programmer who will decide when things are done, even if it isn't officially supposed to be your decision. Ultimately there is a point before releases when you have to push back at PM or whoever and say, "What you are asking for is not a bug fix -- it is a feature request. We are way beyond the point at which we can even be considering introducing new features" It will always be you the programmer who does this because no one else will. 13. Don't check in a lot of code right before a vacation or long weekend. 14. It is easier to not get assigned to something that you don't want to work on than it is to get out of it after it has been assigned to you. Trust your instincts regarding death marches. If some project doesn't feel right, don't get assigned to it. 15. If you find yourself working for a place at which there is a manager who is printing out burn-down charts and hanging them on the wall, start looking for another job. 16. Working for big companies/name software companies can suck more than you can possibly imagine. Just because your friend who works for Company X is always bragging about the rock climbing wall doesn't mean that Company X is a great place to work. 17. For all but the simplest cases, prefer recursive descent parsers over regular expressions. 18. Get good at debugging and learn to use a profiler. 19. Focus on making your way into a position in which you are doing what you like software-wise while you are young and can more easily take risks. 20. Don't break the build. If you do break the build, don't make a lot of excuses -- no one cares -- just fix it quickly. Source: [Quora](http://www.quora.com/What-do-software-developers-age-30-and-over-know-now-that-they-wish-they-had-known-in-their-20s/answers/9185154?fuck=you). *In order to get link without Quora censoring stuff, just add any `?abc=` in the url. My personal favorite is `?fuck=you`, in the hope that it will be popular enough to show in their log someday :D* --- --- date: "2015-01-27T00:00:00Z" link: https://michaelochurch.wordpress.com/2014/07/13/how-the-other-half-works-an-adventure-in-the-low-status-of-software-engineers/ title: 'How the Other Half Works: an Adventure in the Low Status of Software Engineers' --- > Bill had been a Wall Street quant and had “Vice President” in his title, noting that VP is a mid-level and often not managerial position in an investment bank. His current title was Staff Software Engineer, which was roughly Director-equivalent. He’d taught a couple of courses and mentored a few interns, but he’d never been an official manager. So he came to me for advice on how to appear more “managerial” for the VP-level application. A fascinating read by Michael O. Church. Lots of advices on software development careers. Great blog. Subscribed! --- --- date: "2015-01-25T00:00:00Z" link: http://www.les-crises.fr/la-fabrique-du-cretin-defaite-nazis/ title: How movies could change your perception --- It's interesting how much movies can change our perception: survey asking people in France who contributed the most to the defeat of Nazi Germany. sondage nation contribue defaite nazis --- --- date: "2015-01-24T00:00:00Z" title: Fix WELD-001408 unsatisfied dependencies for type error when deploying to GlassFish --- Took me half an hour today to figure this out when deploying to production servers. Apparently, the CDI extension loaded from a JAR in a WAR, thus different classloader, makes it uninjectable. The problem can be fixed by simply disable implicit CDI on GlassFish {{< highlight bash >}} ${GLASSFISH_HOME}/bin/asadmin set configs.config.server-config.cdi-service.enable-implicit-cdi=false {{< / highlight >}} --- --- date: "2015-01-23T00:00:00Z" link: http://strongloop.com tags: - Programming - Node.js title: REST APIs made easy with StrongLoop --- [StrongLoop](http://strongloop.com) allows you to quickly create REST APIs using their graphic interface and CLI. SLC also supports debugging, profiling, tracing, deploying as well as monitoring features. Creating REST APIs with `slc` is as easy as creating datasource and model. StrongLoop will do the rest for you. I was very tempted to use StrongLoop for a recent project but I had to deal with a legacy database that use a very old `odbc` driver which force me to use `node.js` and `express`. --- --- date: "2015-01-22T00:00:00Z" link: http://www.microsoft.com/microsoft-hololens/en-us title: Microsoft HoloLens --- microsoft hololens Microsoft is really on a roll lately. They are on the right track to regain their cool factor among devs. The device looks truly impressive and based on what we know so far, it looks a lot better than Google Glass and Oculus Rift. > When you change the way you see the world, you can change the world you see. It's interesting that Microsoft calls this `holographic technology` instead of `augumented reality`. Though it's not technically correct, they've got themselves a better brand to distinguish their product. There are zillions use case for this kind of technology: healthcare, entertainment, education; you name it. Just to name a few that pop up in my head real quick. * expert coaching * training * modelling * gaming * millitary applications * etc ... Not to mention, whenever a technology like this is introduce, people will find a way to use this in `adult entertainment`. Let's just hope that Microsoft will not overpromise like Google did and let it ended up where Google Glass is now. --- --- date: "2015-01-20T00:00:00Z" description: Simpleast way to create a contact form on jekyll tags: - Jekyll title: Create a contact form with jekyll --- Since `jekyll` blog is static, we have to rely some some kind of service endpoint to send email to our mailbox. Good thing is that there are plenty of services for this purpose: `heroku` for free hosting and `mailgun`, `mandrill` or `mailjet` for sending email. Pick one of your choice. I did a quick search before firing up `Sublime Text` to create a simple app for sending email and luckily, someone already wrote [one](https://github.com/ousenko/simple-contact-form). One less thing to do :) ## Create Heroku and a Mandrill account Sign up an account at Heroku. Download and install `heroku` [toolbelt](https://toolbelt.heroku.com/) while you're at it. For sending email, you can go with [Mandrill](https://mandrillapp.com), [Mailgun](http://www.mailgun.com) or [Mailjet](https://www.mailjet.com). They all come with free plan which is more than enough for personal use. If you pick something other than Mandrill, you will have to edit the app a bit use their own libraries. If you're lazy, just go with Mandrill. ## Install the app on heroku {{< highlight bash >}} heroku auth:login # enter your account credentials when asked git clone https://github.com/ousenko/simple-contact-form.git heroku create heroku config:set MANDRILL_API_KEY= heroku config:set USER_EMAIL= heroku config:set USER_SITE= heroku config:set SUCCESS_PAGE= git push heroku master # deploy {{< / highlight >}} ## Create contact form on your website Create a page with simple form like below. I'm using Bootstrap for my blog so styling is just a matter of adding a couple of CSS classes. {{< highlight html >}} {% raw %}
Email:
Name:
Subject:
Message:
{% endraw %} {{< / highlight >}} ## Adding reCAPTCHA (optional) [Sign up for an API key pair](https://developers.google.com/recaptcha/docs/start) and add it to your page. I don't have a need for this, personally. Here what's mine like contact form with jekyll --- --- date: "2015-01-18T00:00:00Z" title: IO performance benchmark on RamNode --- *Last update: 2015-01-18* I read a IO performance benchmark today comparing AWS vs DigitalOcean. It makes me curious about the IO performance on RamNode. Of course I wouldn't expect something high since I'm running in one of the lowest tier offerred by them (It's not even 100% SSD but just some kind of hybrid SSD-Cached). I will also ignore read test since all the VPS are container-based and read is probably heavily-cached anyway. As for the tool, I'm using `fio` for my IO benchmark. ## Installing fio {{< highlight sh >}} sudo apt-get install fio mkdir data # at ~ I suppose nano testconfig.fio # content of the file [random] rw=randread size=4g directory=/home/username/data iodepth=403 direct=1 blocksize=8k numjobs=10 # check size and your free space left (df -h) nrfiles=1 group_reporting ioengine=sync loops=1 {{< / highlight >}} ## Result Result when `blocksize=8k` {{< highlight sh >}} Run status group 0 (all jobs): READ: io=2048.0MB, aggrb=6485KB/s, minb=6485KB/s, maxb=6485KB/s, mint=323357msec, maxt=323357msec {{< / highlight >}} Result when `blocksize=16k`, `bw` peaks at `21249`. {{< highlight sh >}} Run status group 0 (all jobs): READ: io=1024.0MB, aggrb=8154KB/s, minb=8154KB/s, maxb=8154KB/s, mint=128592msec, maxt=128592msec {{< / highlight >}} ## Result interpretation One thing to notice is the CPU load skyrocketted. Though I only went with `numjobs=2` and `size=1g`, the CPU load hits 3.x (300%). This is expected behavior since I'm on their [Massive VPS plan](https://clientarea.ramnode.com/aff.php?aff=1728). Due to this CPU issue, this is probably not the highest write speed possible as well. I would have to try it on a higher plan where CPU is irrelevant to the test. Too bad, RamNode doesn't allow me to quickly launch an instance to test and destroy after. So overall, DigitalOcean > AWS > RamNode. RamNode is left behind by far margin, probably due to the CPU on their lowest plan. If your application is IO-heavy, you should probably look else where. --- --- date: "2015-01-18T00:00:00Z" tags: - Programming - JavaScript - Node.js title: Fuck callbacks! Let's use generators --- Let's write a simple function `hello` that return a string when called. var hello = function () { return 'Hello ' + name; } Now convert it into a generator. Call `hello()` this time will return you an `Object` instead: the generator. var hello = function *() { return 'Hello ' + name; } Let's consider the following snippet. var hello = function *() { yield 'Stopped here!'; return 'Hello ' + name; } var generator = hello('Clark Kent'); console.log(generator.next()); // {value: 'Stopped here', done: false} console.log(generator.next()); // {value: 'Hello Clark Kent', done: true} So what good can generators do for me? Generators can help eliminiating callbacks. Using callback for 1 or maybe 2 levels is fine. However, imagine if you have to use callbacks for 3 levels or more like the example below. URGH!! func_1(arg, function(err, result) { if (err) { .... } else { func_2(result, function(err, result2) { if (err) { ... } else { func_3(result2, function(err, result3) { if (err) { ... } else { // LOST IN LIMBO !!! } }); } }); } }); Take an example: consider a scenario where you have to establish connection to a bunch of database servers at application startup. With generators, you can do something like this: var init_all_connections = function *() { var result; result = yield init_connection(conn_str1); result = yield init_connection(conn_str2); result = yield init_connection(conn_str3); // ... } For the sake of conveniency, you can write a function to execute the generator until `done` is `true`, `throw` if an error occurs. So the whole thing looks as simple as this: var init_all_connections = function *() { var result; result = yield init_connection(conn_str1); result = yield init_connection(conn_str2); result = yield init_connection(conn_str3); // ... } // execute execute_generator(init_all_connections()); Look how the code is cleanly organized and so much readable compare with the callback hell above. Ain't you glad you read this now ;) --- --- date: "2015-01-17T00:00:00Z" link: https://github.com/petkaantonov/bluebird tags: - Programming - JavaScript title: bluebird - a promise library with unmatched performance --- The documentation of `bluebird` is so much better than `Q` - the library which I'm currently using. `bluebird` with `nodejs` is even better: it can convert an existing promise-unaware API to promise-returning API which is so awesome. Thid feature works with most popular libraries (which use error as first arg (as they all should)). I'm sold!! ### Usage {{< highlight javascript >}} function getConnection(urlString) { return new Promise(function(resolve) { //Without new Promise, this throwing will throw an actual exception var params = parse(urlString); resolve(getAdapater(params).getConnection()); }); } {{< / highlight >}} or with `promisifyAll` method {{< highlight javascript >}} var fs = require("fs"); Promise.promisifyAll(fs); // Now you can use fs as if it was designed to use bluebird promises from the beginning fs.readFileAsync("file.js", "utf8").then(...) {{< / highlight >}} --- --- date: "2015-01-16T00:00:00Z" title: So I upgraded my VPS --- I have little to none demand for web server's power since my blog is static, plus a couple of small web apps I test on the VPS now and then. Recently, my interests shifted to Ionic framework, nodejs, mongodb and angularjs. I had quite a few ideas for pet projects, mostly for the purpose of learning. I decided to upgrade my VPS to a bit better plan - [256MB of RAM](https://clientarea.ramnode.com/aff.php?aff=1728). Still a tiny machine but should be enough for what I have in mind. I've also did a re-installation of the OS as I switch from 64bit Ubuntu 14.04 image to 32bit version. I have no plan to scale this VPS for anything serious so 32bit is good plenty for the purpose, plus all the additional bit of memory. Migration process is rather painless. I just had to click `Re-install` OS, setup new user account, `apt-get` a couple of libs (rvm, ruby, jekyll), setup git server and then `git push` my blog. Everything is back to normal now I suppose. --- --- date: "2015-01-15T00:00:00Z" tags: - Node.js title: Scaling node.js application with cluster --- `node.js`'s single thread default architecture prevents it from utilizing multi-cores machines. When you create a new `node.js` application in WebStorm for example, this is what you get: {{< highlight javascript >}} var debug = require('debug')('myApp'); var app = require('../app'); app.set('port', process.env.PORT || 3000); var server = app.listen(app.get('port'), function() { debug('Express server listening on port ' + server.address().port); }); // in app.listen method app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); }; {{< / highlight >}} This code snippet will create a simple HTTP server on the given port, executed in a single-threaded process; doesn't matter how many cores your machine has. This is where `cluster` comes in. `cluster` allows you to create multiple processes, which can share the same port. Ideally, you should spawn only 1 thread per core. {{< highlight javascript >}} var cluster = require("cluster"), http = require("http"); cpuCount = require("os").cpus().length; port = process.env.PORT || 3000 if (cluster.isMaster) { for (var i = 0; i < cpuCount; ++i) { cluster.fork(); } cluster.on("exit", function(worker, code, signal) { cluster.fork(); }); } else { http.createServer(function(request, response) { // ... }).listen(port); } {{< / highlight >}} That's how you do it on a single machine. With a few more lines of code, you application can now utilize better modern hardware. If this isn't enough and you need to scale across machines, take a look at load balancing using `nginx`. I will cover that in another post. --- --- date: "2015-01-14T00:00:00Z" description: How to make AngularJS works with jekyll since both are using double curly braces syntax tags: - Jekyll - AngularJS title: Using AngularJS with jekyll --- This blog is running on `jekyll`; and since I'm learning AngularJS, I want to try AngularJS on my blog as well. The problem is both are using double curly braces syntax, `jekyll` will consume it first when generating and AngularJS will be left with nothing. In order to use AngularJS with `jekyll`, we have to instruct AngularJS to use different start and end symbol by using `$interpolateProvider`. {{< highlight javascript >}} var myApp = angular.module('myApp', [], function($interpolateProvider) { $interpolateProvider.startSymbol('[['); $interpolateProvider.endSymbol(']]'); }); function MyCtrl($scope) { $scope.name = 'Clark Kent'; } {{< / highlight >}} Using it with the new symbol {{< highlight html >}}
Hello, [[name]]
{{< / highlight >}} That's it. Enjoy hacking your blog. *P/S: I didn't push any change to this blog. I'm just playing AngularJS and `jekyll` locally* --- --- date: "2015-01-14T00:00:00Z" link: https://iojs.org tags: - iojs - Node.js title: io.js v1.0.0 released --- A fork of `node.js` with faster release cycle and open source governance. `io.js` v1.0.0 also marks the return of Ben Noordhuis with third most commits in `node.js` as a technical commitee. For those who don't know about Ben Noordhuis's story[^hn-story]. Here's a short version: * Ben Noorddhuis was a major contributor to NodeJS and a voluntee * Ben Noorddhuis rejected a pull request[^gh-pull-request] that would have made pronoun in the document gender neutral. The documents were already grammatically correct, but whoever made the pull request had a political preference for using a non masculine pronoun. Ben rightly saw this a trivial change and reject. `Issacs` accepted the PR and Ben attempted to revert[^revert-pr] it. * Joyent put an embarrassing and [immature blog post](https://www.joyent.com/blog/the-power-of-a-pronoun) which essentially called Ben an "asshole" and said that if he was an employee he'd be fired. More related links: * [Ben's response](https://github.com/joyent/libuv/pull/1015#issuecomment-29568172) * [Strongloop's response](http://strongloop.com/strongblog/collaboration-not-derision-in-the-node-community/) [^hn-story]: [Hacker News's discussion](https://news.ycombinator.com/item?id=6845286) [^gh-pull-request]: [Pull request](https://github.com/joyent/libuv/pull/1015#issuecomment-29568) [^revert-pr]: [Ben attempted to revert the PR](https://github.com/joyent/libuv/commit/804d40ee14dc0f82c482dcc8d1c41c14333fcb48) --- --- date: "2015-01-11T00:00:00Z" title: A minimal iTerm2 setup --- My current machine is a Macbook Air 11 inches so screen estate is a luxury thing. I've always try to maximise the use of my screen by things like moving Dock to the side (and auto hide, delay set to 0), making use of multiple Mission Control desktop,etc... It always strikes me that even though iTerm2 already supports tmux out-out-the-box, it still has all that title bar and tab bar. They are basically useless to me. So why not getting rid of it? iTerm2 is open-source anyway. You just have to `clone` the repo and edit a bit to get rid of the titlebar. {{< highlight objective-c >}} git clone https://github.com/gnachman/iTerm2.git cd iTerm2 vi sources/PseudoTerminal.m {{< / highlight >}} Search for method `styleMaskForWindowType` and in the `default` case, remove `NSTitledWindowMask`. {{< highlight objective-c >}} default: return (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSTexturedBackgroundWindowMask); {{< / highlight >}} Rebuild and enjoy the new sexy, minimal look of iTerm2. --- --- date: "2015-01-09T00:00:00Z" link: https://www.npmjs.com/package/pretender tags: - Node.js title: Pretender - a mock server library --- > Pretender is a mock server library in the style of Sinon (but built from microlibs. Because javascript) that comes with an express/sinatra style syntax for defining routes and their handlers. {{< highlight javascript >}} var server = new Pretender(function(){ this.put('/api/songs/:song_id', function(request){ return [202, {"Content-Type": "application/json"}, "{}"] }); }); {{< / highlight >}} Very easy to use for quick prototyping. --- --- date: "2015-01-09T00:00:00Z" tags: - AngularJS title: AngularJS diary - Day 4 --- ##Update `ng-model` of one controller from another Using `$broadcast` to broadcast a message (or `$emit`) and put listener on that message on receiver. {{< highlight javascript >}} // unrelated controllers -> $rootScope broadcasts App.controller('Ctrl1', function($rootScope, $scope) { $rootScope.$broadcast('msgid', msg); }); App.controller('Ctrl2', function($scope) { $scope.$on('msgid', function(event, msg) { console.log(msg); }); }); {{< / highlight >}} When to use `$broadcast` and when to use `$emit`? * `$broadcast` — dispatches the event downwards to all child scopes, * `$emit` — dispatches the event upwards through the scope hierarchy. In case there is no parent-child relation, use `$rootScope` to `$broadcast`. ##Making AJAX call the right way Use `service`, `factory` or `providers` for that. It's advised not to make AJAX calls within the controller. {{< highlight javascript >}} var ExampleService = angular.module("ExampleService", []); ExampleService.service('ExampleService', function($http) { this.my_method = function() { var url = 'http://...'; var req_params = {}; return $http.post(url, req_params); // a promise }; }) // using it ExampleService.my_method().then(function(result) { $scope.my_var = result.data; }); {{< / highlight >}} ##Services vs Factory In most cases, both can be used interchangably. When you’re using `service`, Angular instantiates it behind the scenes with the `new` keyword. Because of that, you’ll add properties to `this` and the service will return `this`. When you pass the service into your controller, those properties on `this` will now be available on that controller through your service. This is why `service` is most suitable for generic utilities. `factory` is useful for for cases like when you don't want to expose private fields but via public methods instead. Just like a regular class object. --- --- date: "2015-01-08T00:00:00Z" tags: - AngularJS - Programming title: 'The next bullet on my resume: AngularJS' --- I started a HTML5 hybrid mobile app project recently. Before I start coding the frontend, I had to decide which client side JavaScript framework I will be using. The choice narrows down between AngularJS (Ionic framework) and Ember. Of those two, AngularJS is obviously the more popular choice. There're many frameworks built on AngularJS; thousands of questions on StackOverflow; lots of open-source projects base on AngularJS on GitHub. There are many great frameworks out there but few has gained so much developer mindshare like AngularJS. There's clearly something about this framework. I mean, take a look at the search trend of AngularJS vs other frameworks. ![angularjs search trend vs others](/img/angularjs-search-trend.png) So I decided to take sometimes to evaluate AngularJS first to see what's all the hypes are about. I downloaded AngularJS; played around; read documents, tutorials and [top questions on StackOverflow](http://stackoverflow.com/questions/tagged/angularjs?sort=votes&pageSize=50). I don't know much about AngularJS yet but I will start blogging about it as I learn here. These are the few things that I've learnt about AngularJS in the last two days. ## AngularJS is easy to start with The thing about AngularJS is it's very intutive, simple to start and easy to understand if you're already familiar with MVx pattern. It has a very high WOW factor (data binding for one) at the beginning if you're coming from, say jQuery. Seeing things like this make people want to explore the framework further. This is one of the main reason that make AngularJS popular, I think. ## Single source of truth > Single Source Of Truth (SSOT) refers to the practice of structuring information models and associated schemata such that every data element is stored exactly once Suppose that you have a toggle button, if you were to use jQuery, you will have to add an `active` CSS class to style when it's toggle on; remove it and re-add `inactive` class when it's off. If there's data dependent on it, you will have to manipulate the DOM yourself. Crazy right! The problem is that it may go out of sync (toggle is on but css class is still `inactive` for example). Plus, the code will be much longer and unnescessary complicated. AngularJS seperates data from its presentation and offer you data binding. You create a view, bound a field to an attribute in your model. Your data model is now single source of truth. You don't have to manipulate with the DOM yourself but dealing with the model instead. ## $scope There's no base model/object in Angular. Whatever you put inside `$scope` is your model. This is a bit weird coming from OOP language. I wonder how would we share the model outside of the `$scope`. The data is stucked between your template and `$scope`. So far, I've seen example using `$rootScope` but it's like using global variables which I'm against unless really nescessary. `factory` seems to be the thing I'm looking for. I shall read more documentations about this. That's it for today. I will continue once I found more cool stuff worth sharing about Angular. --- --- date: "2015-01-04T00:00:00Z" description: A simple tutorial on how to setup a seedbox running rtorrent/rutorrent on Ubuntu VPS title: How to setup rtorrent, rutorrent on Ubuntu --- This is a simple and concise tutorial on how to setup a seedbox running `rtorrent` with `rutorrent` as webui on Ubuntu OS. I've tried to simplify as much as possible to make it easy to understand. It may look a bit lengthy but it's copy-paste-fu mostly. ## Initial server setup Login to your server and create a new user account, add it to `sudo` group. Substitute `USER_NAME` with your desired username. {{< highlight bash >}} ssh root@YOUR_SERVER_IP_ADDRESS adduser USER_NAME gpasswd -a USER_NAME sudo {{< / highlight >}} ## Setup key authentication It's better, less hassle and a lot safer. You should use it. {{< highlight bash >}} # at your local machine: gen key and upload it to your vps ssh-keygen -t rsa cat ~/.ssh/id_rsa.pub | ssh USER_NAME@YOUR_SERVER_IP_ADDRESS 'cat >> .ssh/authorized_keys' {{< / highlight >}} You can try logging in your server with the new user. It will not ask you to enter password this time. ## Disable root login Use `nano` to change `PermitRootLogin` to `no`. `Ctrl+O` to save and `Ctrl+X` to quit afterward. {{< highlight bash >}} nano /etc/ssh/sshd_config service ssh restart {{< / highlight >}} ## Setup rtorrent {{< highlight bash >}} # install libraries required to build rtorrent sudo apt-get update sudo apt-get install subversion build-essential automake libtool libcppunit-dev libcurl3-dev libsigc++-2.0-dev unzip unrar-free curl libncurses-dev libxml2-dev # download source and install cd ~ mkdir src cd src svn checkout http://xmlrpc-c.svn.sourceforge.net/svnroot/xmlrpc-c/stable xmlrpc cd xmlrpc ./configure --prefix=/usr --enable-libxml2-backend --disable-libwww-client --disable-wininet-client --disable-abyss-server --disable-cgi-server --disable-cplusplus make make install {{< / highlight >}} Install `libtorrent` {{< highlight bash >}} cd ~ wget http://libtorrent.rakshasa.no/downloads/libtorrent-0.13.4.tar.gz tar xvf libtorrent-0.13.4.tar.gz cd libtorrent-0.13.4 ./autogen.sh ./configure --prefix=/usr make make install {{< / highlight >}} Install `rtorrent` {{< highlight bash >}} cd ~ # back to home wget http://libtorrent.rakshasa.no/downloads/rtorrent-0.9.4.tar.gz tar xvf rtorrent-0.9.4.tar.gz cd rtorrent-0.9.4 ./autogen.sh ./configure --prefix=/usr --with-xmlrpc-c make make install ldconfig {{< / highlight >}} Create new user to run `rtorrent` and required folders {{< highlight bash >}} useradd -d /home/rtorrent_usr/ rtorrent_usr mkdir /home/rtorrent_usr mkdir /home/rtorrent_usr/downloads mkdir /home/rtorrent_usr/.session mkdir /home/rtorrent_usr/watch mkdir /home/rtorrent_usr/.sockets touch /home/rtorrent_usr/.sockets/rpc-socket nano /home/rtorrent_usr/.rtorrent.rc # update permission chown -R rtorrent_usr:rtorrent_usr /home/rtorrent_usr/ chown -R www-data:www-data /usr/share/nginx/html {{< / highlight >}} For `rtorrent` config, you can copy the default one and mess around. It's simple and straight forward. I won't go into details here. {{< highlight bash >}} cp /usr/share/doc/rtorrent/rtorrent.rc ~/.rtorrent.rc {{< / highlight >}} ## Setup nginx/rutorrent for webui Install `nginx` and `php5-fpm` to run `rutorrent` {{< highlight bash >}} sudo apt-get install nginx php5-fpm php5-cli {{< / highlight >}} By default, your root folder will be at `usr/share/nginx/html`. Create a configuration file for `rutorrent` {{< highlight bash >}} nano /etc/nginx/sites-available/rutorrent {{< / highlight >}} Copy and paste the content below {{< highlight bash >}} server { listen 80; listen [::]:80 default_server ipv6only=on; ## listen for ipv6 root /usr/share/nginx/html; index index.php index.html index.htm; server_name localhost; location / { try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # php5-fpm location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; # With php5-fpm: fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } } {{< / highlight >}} Create symlink to `sites-enabled` and restart `nginx` {{< highlight bash >}} cd /etc/nginx/sites-enabled ln -s ../sites-available/rutorrent # restart nginx service nginx restart {{< / highlight >}} Create a test file to verify `php5-fpm` is working {{< highlight bash >}} nano /usr/share/nginx/html/info.php # use content below {{< / highlight >}} Download `rutorrent` {{< highlight bash >}} cd /usr/share/nginx/html svn checkout http://rutorrent.googlecode.com/svn/trunk/rutorrent svn checkout http://rutorrent.googlecode.com/svn/trunk/plugins rm -r rutorrent/plugins mv plugins rutorrent/ {{< / highlight >}} And that's it. Start `rtorrent` and things should work as it supposes to. Feel free to ask me any question if you got stuck. If you're a casual torrent user like me and still looking for a dead-cheap, torrent-friendly VPS provider, I may recommend you to take a look at [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728). They provide a [$15 per YEAR](https://clientarea.ramnode.com/aff.php?aff=1728) for 80GB of space and 500GB bandwidth. As long as you don't do heavy torrenting and public trackers, you should be safe. Cheers! --- --- date: "2015-01-03T00:00:00Z" description: Using GitHub issue tracker as comment system for your static blog tags: - Jekyll title: Using GitHub issue tracker as comment system for your static blog --- Ivan Zuzak [wrote about it here](http://ivanzuzak.info/2011/02/18/github-hosted-comments-for-github-hosted-blogs.html). It's actually a very cool idea. All you need is to create a new repo on GitHub, create an issue for the blog post you want to enable comment and set `commentIssueId` in your blog post. A JavaScript ajax call will pull comments from GitHub on page load. If I were to enable comments on my blog, I would defenitely go this route. You already blog like a hacker, you should also comment like one. I've also seen people using [Discourse](http://www.discourse.org) for their website's comments. The downside is setting up a new VPS and install Discourse(an online discussion software) seems like overkill for this purpose. Also, Discourse is not exactly lightweight. In order to install Discourse, you need a VPS with minimum 1GB of RAM (at least that was when I last tried it). My VPS has mere 128MB of memory, not exactly a beasty machine. The reason I went with static blog is it's very lightweight. Using Discourse kinda defeat the purpose now, doesn't it? *If you don't want to go through all the troubles, you can use Disqus instead.* --- --- date: "2015-01-02T00:00:00Z" title: Resume-driven developer --- A resume-driven developer can't seem to use the same technology, in the same way all over again. When assigned an easy task, he will find ways to over-engineering a solution that will make it less boring. He loves injecting new technology into the current project, often without any upfront with other developers (well, this could be very bad since it may introduces competing UI framework, multiples data access methods, repeated functionalities, etc..). And when asked about it, he wil talk for hours about how horrible the current code/framework in the existing project is. Does all of that sound familiar to you? If yes, you're definitely a resume-driven developer. I have to confess I'm that guy. I'm easy attracted to shiny new, cool technology but I found it hard to find motivation to learn it properly. Working in an actual project helps me learn better with all the challenges and deadline. Decide what's the next bullet you want to put on your resume at the end of the project, be it new language, new framework or some responsibilities, request to take that part of the project and make them true. Resume-driven in itself isn't bad. It's actually a sign of a passionate developer. As long as it makes sense to introduce it to the current project without adding too much complexity, it will work. Remember, [your code is not for you to read alone](/2014/11/27/shorter-code-is-better/). It's your job to manage the complexity, not to create it. --- --- date: "2015-01-01T00:00:00Z" tags: - Machine learning title: Shower thought about movie recommedation --- Once in awhile, when I see a movie that I like, I go to IMDB and find out who directed it; who are the main actor/actress; what are the others highly rated movies of that director, main actor/actress. Then I will go on systematically watching one film after another. Given that they are good enough to keep me going. The result is decidedly mixed. It doesn't always turn out to be good as I want but it works, to a degree. The thing about movie is that it takes million of dollars to make and it involves many parties. It's hard for the creator to make the movie the way they indended to make. And I guess it makes sense. Making movie is unlike writing a novel; at least not until technology evolves to make producing movies cheap enough to be an habit like writing novels. When that happened, recommender system for movies will be a lot easier to implemented and more accurated. --- --- date: "2014-12-28T00:00:00Z" description: Using .woff fonts to further reduce number of blocking CSS resources, with fallbacks to make it compatible with old browsers. tags: - Web performance title: Inline Google fonts to further reduce number of blocking CSS resources --- [Most major browsers](http://caniuse.com/#feat=woff) have support for `.woff` font format for quite awhile now. The number is around 85%+ with IE8 is the most high profile exception. But ... who actually cares about IE8 right! Chances are if you are reading my blog, you won't be using it anyway [^1]. Ok, I'm convinced. How do I download and use `.woff` fonts now? There is a [npm package](https://github.com/mmastrac/webfont-dl) called `webfont-dl` which will create a ready-to-use CSS file with `.woff` fonts inline and other font formats as well as fallback. Including it in your main stylesheet and you're done. Maybe organize it in a `/fonts/` folder and update the font links in the CSS. That's it. To install `webfont-dl`: (`nodejs` required) ~~~ npm install -g webfont-dl ~~~ To download the CSS and webfonts: ~~~ webfont-dl "http://fonts.googleapis.com/css?family=Crimson+Text:400,400italic|Raleway:500" \ -o css/font.css ~~~ [^1]: I provide fallbacks for unsupported browser anyway. See how nice I am ;) --- --- date: "2014-12-27T00:00:00Z" description: A simple command to burn a bootable ISO file to an USB stick on OS X. No need to install any extra software. title: How to burn a bootable ISO file to USB stick on OS X --- This method works with *unix ISO files. If you want to create a bootable Windows USB, use `Bootcamp Assistant` instead. {: .alert .alert-info} OS X shipped with `dd` installed. It's a little utility to copy/convert from an input (your ISO file) to another output (your USB stick). The process is fairly easy. Plugin your USB stick. Find where your USB stick is mounted by looking at the name and the size. It looks like `/dev/diskX`. ~~~ diskutil list ~~~ To burn the bootable ISO to USB stick, issue the following command. Replace the ISO file name with the path to your ISO file and `diskX` with the value you noted at the previous step. ~~~ sudo dd if=xubuntu-14.10-desktop-amd64.iso of=/dev/diskX ~~~ Enter your password, wait for a little while. It may looks like it's hanging but do not abort. It would take few minutes, depends on the size of the ISO file. If you find `dd` too slow, you can try replace `/dev/diskX` with `/dev/rdiskX` and add `bs=4m` option. ~~~ sudo dd if=xubuntu-14.10-desktop-amd64.iso of=/dev/rdiskX bs=4m ~~~ --- --- date: "2014-12-27T00:00:00Z" description: Contexts.app is the perfect companion app for Amethyst title: Contexts is the perfect companion app for Amethyst --- `⌘-Tab` for application switching is a real nuisance. It's application-based instead of document-based. Example, you have two documents, opened in two windows of an app. `⌘-Tab` onlys show 1 entry (the app itself) in the application switcher, which is annoying. You have no way of getting to the window of the document that you want. Instead you will have to right click on the app icon in the dock and select from there. [Contexts](https://contexts.co) is a simple app that solves a lot of problems with builtin `⌘-Tab` app switcher. ## Switching apps with Contexts Contexts overrides the system shortcut `⌘-Tab` so it will just fit right in. The usual workflow when you want to switch to an app goes like this: you press `⌘-Tab`, take a look at the list and keep pressing `Tab` until you get to the application you want to switch to. It's far from idea because you can't jump straight to the app you want but keep holding `⌘-Tab` while checking the list. Contexts solve this problem by showing a small sidebar with badge number. All you have to do is to press `⌘` + `number`[^1] to switch to the coresponding app. Another problem is with the Finder app. When I need to open a new Finder window when there isn't any opened. I will have to `⌘-Tab` then `Tab` to it. Finder will be brought to front without any active window. I then have to press `⌘-N` combination to open a new window. So much trouble for a simple operation. Contexts fixes this by openning a new window when there isn't one. ## Switching app by searching Switching app by searching is by far my favorite feature. Contexts allows you to search by pressing `Ctrl+Space` then type app name or document's title. This is a much improvement over the builtin application switcher. I can now switch app without the need to hold `⌘-Tab`, which I found very tiresome. ![contexts app os x](/img/contexts-search.png) I only use `⌘-Tab` to quickly switch back to the previous app because Contexts sorts apps by default that way. ## Fin I fell in love with Contexts [sometimes ago](2013/09/07/contexts-for-mac/). Then there was Amethyst. These two apps just complements each other perfectly, making managing app windows a breeze. If you've never tried it, give it a shot. Once you do, you won't go back. Guaranteed. [^1]: I change the combination to `Alt+number` since it messes up with Chrome tab switching (`⌘-number`) shortcuts. --- --- date: "2014-12-26T00:00:00Z" description: A curated list of maching learning frameworks, libraries and softwares. link: https://github.com/josephmisiti/awesome-machine-learning tags: - Machine Learning title: Awesome maching learning --- > A curated list of awesome machine learning frameworks, libraries and software (by language). Inspired by awesome-php. Other awesome lists can be found in the [awesome-awesomeness](https://github.com/bayandin/awesome-awesomeness) list. Stumble upon this list when I was looking for some Julia's libraries. --- --- date: "2014-12-24T00:00:00Z" description: Loading custom font is slow even if you're using CDN. You can use deferred font loading and make use of localStorage for storing loaded font. tags: - Web performance title: Deferred font loading and using localStorage as cache --- The idea is to lazy-load web fonts once everything else loaded completely, store them in `localStorage` to be used for subsequent pages. A cookie is used as a flag to check if the fonts are cached in `localStorage`. There are something to note: - If visitors disable cookie, this method will backfire as the webfont will be reloaded everytime visitors go to a different page. - `localStorage` is slower than browser cache (So i heard). - A browser that supports `localStorage` is required, which mean IE8+, Firefox, Opera and Chrome. - On first load, the page will be flashed a bit after finish loading web fonts. This happened because the web fonts finish loading and are re-applied onto the page. Because of this particular reason, I refuse to use this method to lazy-load web fonts. I just hate it seeing it flashed. I'm a sucker for this kind of detail. {{< highlight javascript >}} (function () { "use strict"; // once cached, the css file is stored on the client forever unless // the URL below is changed. Any change will invalidate the cache var css_href = './index_files/web-fonts.css'; // a simple event handler wrapper function on(el, ev, callback) { if (el.addEventListener) { el.addEventListener(ev, callback, false); } else if (el.attachEvent) { el.attachEvent("on" + ev, callback); } } // if we have the fonts in localStorage or if we've cached them using the native batrowser cache if ((window.localStorage && localStorage.font_css_cache) || document.cookie.indexOf('font_css_cache') > -1){ // just use the cached version injectFontsStylesheet(); } else { // otherwise, don't block the loading of the page; wait until it's done. on(window, "load", injectFontsStylesheet); } // quick way to determine whether a css file has been cached locally function fileIsCached(href) { return window.localStorage && localStorage.font_css_cache && (localStorage.font_css_cache_file === href); } // time to get the actual css file function injectFontsStylesheet() { // if this is an older browser if (!window.localStorage || !window.XMLHttpRequest) { var stylesheet = document.createElement('link'); stylesheet.href = css_href; stylesheet.rel = 'stylesheet'; stylesheet.type = 'text/css'; document.getElementsByTagName('head')[0].appendChild(stylesheet); // just use the native browser cache // this requires a good expires header on the server document.cookie = "font_css_cache"; // if this isn't an old browser } else { // use the cached version if we already have it if (fileIsCached(css_href)) { injectRawStyle(localStorage.font_css_cache); // otherwise, load it with ajax } else { var xhr = new XMLHttpRequest(); xhr.open("GET", css_href, true); // cater for IE8 which does not support addEventListener or attachEvent on XMLHttpRequest xhr.onreadystatechange = function () { if (xhr.readyState === 4) { // once we have the content, quickly inject the css rules injectRawStyle(xhr.responseText); // and cache the text content for further use // notice that this overwrites anything that might have already been previously cached localStorage.font_css_cache = xhr.responseText; localStorage.font_css_cache_file = css_href; } }; xhr.send(); } } } // this is the simple utitily that injects the cached or loaded css text function injectRawStyle(text) { var style = document.createElement('style'); // cater for IE8 which doesn't support style.innerHTML style.setAttribute("type", "text/css"); if (style.styleSheet) { style.styleSheet.cssText = text; } else { style.innerHTML = text; } document.getElementsByTagName('head')[0].appendChild(style); } }()); {{< / highlight >}} I did not write this code. This bit was taken from [Smashing Magazine](http://www.smashingmagazine.com) - [source](https://gist.github.com/hdragomir/8f00ce2581795fd7b1b7). {: .alert .alert-info} --- --- date: "2014-12-23T00:00:00Z" description: Asynchronous code war between callback and promise tags: - Node.js title: 'nodejs: callback vs promise' --- http://www.slideshare.net/wookieb/callbacks-promises-generators-asynchronous-javascript ## The problem Consider the code snippet below: {{< highlight javascript >}} var x = doSomething(); typeof x === 'undefined'; // true {{< / highlight >}} The second statement will not wait for the first one to complete, hence result in `true` in the second statement. When it comes to dealing with asynchronous in nodejs, we usually come down to 2 most popular options: callback and promise. ## Callback Callback is widely used but when we need 3 or more operations going in sequence, things are going to get ugly. Consider the following code snippet. {{< highlight javascript >}} var doSomething = function(arg1, arg2, cb_fn) { var sum = arg1 + arg2; cb_fn(sum); }; // usage doSomething(10,20, console.log); {{< / highlight >}} That is just one function with callback. Imagine this with 4 levels of callback. Welcome to CALLBACK HELL!! {{< highlight javascript >}} var func_1 = function(cb) { func_2(function(err, result) { if (err) { cb(err); return; } func_3(result, function(err, result) { if (err) { cb(error); return; } // WE NEED TO GO DEEPER }); }); } {{< / highlight >}} ## Promise What is a [promise](https://www.promisejs.org)? The core idea behind promises is that a promise represents the result of an asynchronous operation. A promise is in one of three different states: * pending - The initial state of a promise. * fulfilled - The state of a promise representing a successful operation. * rejected - The state of a promise representing a failed operation. Once a promise is fulfilled or rejected, it is immutable (i.e. it can never change again). The most complete library for promise on Nodejs is `Q`. --- --- date: "2014-12-23T00:00:00Z" description: How to fix `Logon failed for login 'username' due to trigger execution` error with SQL Server tags: - Node.js title: How to fix `Logon failed for login 'username' due to trigger execution` error with mssql --- I recently tumbled upon an error when trying to connect to SQL Server in nodejs using `mssql` package. ~~~ Logon failed for login 'username' due to trigger execution ~~~ I have no problem connecting to a dev db server of mine, probably due to I don't have any restriction/security setup on it. I only got this when connecting to a production server. Problem lies in the default driver `tedious` used by `mssql`. Changing to `tds` fixes the problem. I don't actually understand the problem behind this so if anyone knows, please help explain it to [me](https://twitter.com/tuananh_org). --- --- date: "2014-12-22T00:00:00Z" tags: - Machine Learning title: How to setup Sublime-IJulia with Sublime Text --- I love Sublime Text. I prefer to do all of my development in Sublime Text if possible, especially when Julia Studio or Juno don't offer much addtional features. The instructions on GitHub repo is a bit confusing so I decided to rewrite it steps by steps how to set Sublime-IJulia up on OS X. I can't say for other OS though. I suppose that you have already had `homebrew` and `julia` installed (v0.3.3 as of this post) and add `julia` to PATH variable. ### Install zeromq and ipython From terminal app, install `python` and `zeromq` {{< highlight bash >}} # instaling requirements brew install python zeromq pip install ipython {{< / highlight >}} After that, launch `julia` console and install `ZMQ` and `IJulia` packages. `ZMQ` and `IJulia` should be added and builded successfully. {{< highlight julia >}} Pkg.add("ZMQ") Pkg.add("IJulia") {{< / highlight >}} ### Install Sublime-IJulia Now head to Sublime Text and install `Sublime-IJulia` by pressing `Ctrl+Cmd+P` and type `Install Package`, search for `Sublime-IJulia`. Follow the instruction from step 7 of [this GitHub repo](https://github.com/quinnj/Sublime-IJulia). There are a few things to note though: - Change zeromq dylib path because of the version difference. - If you got `Kernel died` error message, try using absolute path for `julia` even though you already have it in `$PATH` variable. That's it. Follow the instructions carefully and it will work as expected. --- --- date: "2014-12-21T00:00:00Z" description: A short conversation with a total stranger. title: A conversation with stranger --- **Stranger**: So you've been living here for nine years? How come you don't speak Thai? **me**: Well... (chuckle) **Stranger**: What do you do for a living? **me**: I'm a programmer. I write softwares. **Stranger**: Aha. So you're a nerd? You don't feel the need to talk to people. **me**: Well...yes. --- --- date: "2014-12-19T00:00:00Z" description: How to include social sharing buttons on your website without embedding social script crap title: Social sharing button without embedding sharing script crap --- Found [this collection](https://github.com/bradvin/social-share-urls) share url structure from various social networks on Github. Example, for Twitter: ~~~ https://twitter.com/share?url={url}&text={title}&via={via}&hashtags={hashtags} ~~~ ![social sharing buttons](/img/social-sharing-btn.png) Use this along with font-awesome icons and we have some nice social sharing buttons without tracking scripts or slowing down your website one bit. --- --- date: "2014-12-18T00:00:00Z" title: When static website isn't fast enough --- I have recently taken a look at my website loading speed again. A bit about my website, as of currently: * I hosted my website on a smallest, cheapest VPS plan at [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728) ($15 per year, massive plan) which shares the physical machine with a numerous other people. * This website is powered by jekyll - a static site generator. * The website is using a custom theme, based on Bootstrap. I loaded two addtional fonts from Google Fonts (Ubuntu and Merriweather) for heading and paragraph. * I have Jquery and Bootstrap script on my page for some fancy stuff I might need later, along with Google Analytics script (async). ## Testing As for the testing setup, I will compare the loading speed of two posts with text only. | | Gtmetrix | Webpagetest | |:-----------------:|:--------:|:------------:| | Total load time | 2.8s | 2s | | Size | 247KB | 221KB | | Number of request | 14 | 17 | | Location | Canada | US | | PageSpeed | 98 | 96 | | YSlow | 96 | - | |-------------------|----------|--------------| {: .table-responsive} 2-3 seconds to load isn't too bad itself but it's definitely not good enough for me. My idea number would be something less than 1 second. I know that custom fonts come with the trade of loading speed but I would expect it to load faster than this, especially when they're served by Google. Stuff I have already tried: * Combine CSS into a single file, minified and served by CloudFlare. * Images are optimized with ImageOptim for optimal size. * Defer/async script when possible. Loading JS script right before `` tag. Webpagetest gives my website A for everything except: First Byte Time (B) and Cache static content (C). Let's break it down. #### First Byte Time * 845 ms First Byte Time * 717 ms Target First Byte Time Apparently, the VPS my website is on is slow (which is expected). I can't do much to enhance this either. #### Cache static content Webpagetest also complains about there's no `max-age` or `expires` set in header on `fonts.googleapi.com`. I have no idea why Google didn't set `max-age` or `expires` for this. Maybe someone with better understanding about this can help explaining to [me](https://twitter.com/tuananh_org). As much as I would like to make my website load faster (I'm sucker for this kind of stuff), I either have to upgrade the VPS plan ($$$) or go with web safe fonts (Ewwwww!). I guess I would have to settle with this for now. Hopefully, you don't find this website too slow. --- --- date: "2014-12-16T00:00:00Z" title: New theme --- My blog is now using customized [Shiori theme](http://ellekasai.github.io/shiori/) - a Bootstrap-based theme for jekyll by Elle Kasai. The switching process is painless. I just have to copy my data over from old jekyll instance, tweak here and there a bit and `git push`. BAMM! The blog now has a shiny new modern look :) ![new theme](/img/new-theme.png) --- --- date: "2014-12-13T00:00:00Z" title: BAT script to execute commands on remote Unix machine with PuTTY --- ~~~ putty -i private_key.ppk remoteuser@remotehost -m local_script.bat ~~~ {: .bash} In the example above, I use public key authentication to authenticate and then execute the commands inside `local_script.bat` on the remote machine. Alternatively, you can use `plink` which is basically `putty` without the GUI part. All other options are basically the same. You can also use password for authentication, just replace `-i` option with `-pw` and your password. --- --- date: "2014-12-07T00:00:00Z" link: http://rg3.github.io/youtube-dl/ title: youtube-dl - A must have little gem in your toolbelt --- > youtube-dl is a small command-line program to download videos from YouTube.com and a few more sites. Installing `youtube-dl` is as easy as `brew install youtube-dl` on OS X. `youtube-dl` allows you to download from many online audio/video sharing websites, not just Youtube. You can use it to download all videos from a playlist, from a channel or simply extracting the audio from a Youtube link. From a channel: ~~~ youtube-dl -citw ytuser:channel_name ~~~ From a link/playlist ~~~ youtube-dl youtube_link ~~~ Listing all available formats: ~~~ youtube-dl -F OKbtC223e30 ~~~ Download a Youtube video as audio ~~~ youtube-dl -f bestaudio OKbtC223e30 ~~~ `youtube-dl` is included in many distro's repo but I recommend you to install from the main website for quicker update. The version in the repo is usually very outdated. --- --- date: "2014-11-27T00:00:00Z" tags: - Programming title: Shorter code is better? --- ![wtfs/m](/img/wtfs_m.jpg) Which one do you prefer: shorter code or more legible code? As for me, legibility of the code always come first. Working in a team will make one realize the importance of code legibility sooner or later. Your code is not for you alone to read. It should be friendly and easy for whoever has to maintain your code as well. It's not easy to write elegant code but it's much easier to write legible code. * Always give a good name to your variables. It's not random naming was talked as one of the hardest problems in computer science. > "There are only two hard things in Computer Science: cache invalidation and naming things." > > *- Phil Karlton* * Comments: comment is not a bad thing. But lots of comment definitely is. It might be a signal that the code is not legible enough that it needs that much lines of comment for readers to be able to understand. Also, it's not a good thing to comment code and leave it there. It will create confusion for those who maintains it. * Uncouple your code. Uncoupling increases legibility and writing test unit a lot easier. * Write everything as if you were to write an API, as it should be intuitive and easy to understand. Your code should be easy to make changes when needed. It should not break a whole system when does. > "Writing specifications is like writing a novel. Writing code is like writing poetry." Your code can talk, so tell a story. --- --- date: "2014-11-26T00:00:00Z" link: http://www.popularmechanics.com/technology/aviation/safety/4219452 title: Where is the safest seat on airplane? --- Business class seat doesn't look very tempting all of sudden. > So when the "experts" tell you it doesn't matter where you sit, have a chuckle and head for the back of the plane. And once your seatbelt is firmly fastened, relax: There's been only one fatal jet crash in the U.S. in the last five-plus years. ![airplan seat survival rate](/img/airplane-seat-survival-rate.gif) --- --- date: "2014-10-18T00:00:00Z" tags: - Keyboard - Review title: Filco Majestouch 2 Ninja Italian Red (TKL) review --- This is a short review of a Filco Majestouch 2 Ninja TKL (Italian Red version). I ordered mine from eBay and the keyboard arrived 3 days later. The keyboard came with some additional keycaps (WASD and Escape keys in red and black) and a keycap puller. ## First impression This thing is sexy as hell. This was one of the main reason I purchase this board to begin with. The case feels really sturdy. Let me put it this way: the HHKB case is very solidly built, this keyboard's case is even better. That's how good it feels when you hold the keyboard. There's no backlit, media keys, OS X friendly keys (can be solved with software), usb hub built-in or detachable cable. Some of those are nice features to have but not essential to me. I personally hate backlit and think it's to gaming-y. I bought this board because it's a simple, no bullshit keyboard. Overall, I'm very satisfied with the aesthetic of this keyboard. ## The switches I went with the blue switches option. It's my personal favorite choice of Cherry switch. One downside as you probably already have known is this thing is really loud. I mean REALLY loud. If you want to make your colleagues secretly hate you, buy one of those blue switch mechanical keyboard. ## The keycaps The default keycaps look pretty good and it matches well with the whole design. I've always wanted a set of Dolch or Granite keyset but I don't think it's going to look good with this board. I haven't had the luxury of trying them out yet though. There were reports of fading key labels with other Filco's keyboard so they release this keyboard with front face printing keycaps. I think it's a lame reason but it looks pretty cool though. For this keyboard's price range, it should have shipped with thick PBT keycaps instead of [coated ABS](http://support.wasdkeyboards.com/customer/portal/articles/1385384-uv-printing). ## Conclusion This keyboard is a simple, no-bs, very high quality keyboard. I love everything about this keyboard: the look, the feel and the sound. Though it's a bit more expensive than the competitors' keyboards, it's totally worth it IMO. The fact that it is sturdier than a premium keyboard (HHKB) says it all. It's probably the best TKL keyboard with Cherry switches available right now. --- --- date: "2014-10-12T00:00:00Z" tags: - Keyboard title: Filco Majestouch 2 Italian Red (TKL) with blue switches --- My latest addition to my keyboard collection: an Italian Red Filco Majestouch 2 (tenkeyless version) with blue switches. It's really hard to find a shop with Filco in stock here in Thailand. I could only find one with black switch in stock, which I am not a huge fan of as it's the linear type with no tactile feedback and too stiff for my taste. Other shops mostly stock Razer, Rocco, SteelSeries and Ducky. I was a bit hesitate at first with this purchase because I thought this board uses red switches (which is like black but less stiff) but turn out, it comes with blue one which is my favorite. The keyboard comes with a keycap puller and some alternative keycaps: WASD cluster, 2 windows key and an `Esc` key, all are in red. ![](/img/filco_majestouch2_italian_red_1.jpg) ![](/img/filco_majestouch2_italian_red_2.jpg) My next step is waiting for someone to sell their sexy SP's Dolch (or Granite, or both :D) keyset to go with this board. In the meantime, this stock keyset probably will do just fine. --- --- date: "2014-09-19T00:00:00Z" description: How to find list of tables which contains a column with certain name and value in Oracle tags: - SQL title: How to find table name when knowing column name and column value in Oracle --- A tester in my project asked me today for a little help: how to find a list of tables that has column name `A` with value `B`. So I wrote a short script and sent him. I thought I would share it here too, in case if might be useful to someone. Usually cases like this, you will query from `user_tab_cols` or `all_tab_cols` for list of tables that has a column with that name and find out. The query below will do exactly that but with the loop added to make searching easier. Make sure you have DBMS output enabled. {{< highlight sql >}} declare col_name varchar2(100) := 'COLUMN_NAME'; search_value varchar2(100) := 'COLUMN_VALUE'; cnt number := 0; cursor c is select distinct table_name from all_tab_cols where upper(column_name) = upper(col_name); begin for e in c loop cnt := 0; execute immediate 'select count(' || col_name || ') from ' || e.table_name || ' where to_char(' || col_name || ') = to_char(''' || search_value || ''')' into cnt; dbms_output.put_line('Found ' || cnt || ' record(s) in ' || e.table_name); end loop; end; {{< / highlight >}} Sample output: ~~~ Found 0 record(s) in TABLE_NAME Found 1 record(s) in TABLE_NAME1 Found 0 record(s) in TABLE_NAME2 ~~~ --- --- date: "2014-09-14T00:00:00Z" description: Some examples of useful use cases for Java Reflection tags: - Programming title: Some useful use cases for Java Reflection --- While most will recommend you not to use Reflection because: * Lose ability to refactoring because you hard-code the name of class/method as String. * Lower runtime performance, * No compile time safety check. However, there're many cases Reflection can become very useful that it pros might outweight its cons. Or it's just cool to show colleagues some new cool tips. ## Easier to implement toString() method for debugging You can use Reflection to loop through an Object's attribute and print it out instead of using StringBuilder to append it yourself. This feature is available in [Apache Common Lang](http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/ReflectionToStringBuilder.html). Or you can write it yourself if all you need is this method. ~~~ public String toString() { return ReflectionToStringBuilder.toString(this); } ~~~ *Note: This is actually not used often because modern IDE(IntelliJ, Eclipse, NetBean, ...) already has 'generator' for this kind of thing.* ## Create object/call method using name This is one of the reasons why Reflection exists in the first place. It's useful in case like you have some configuration stored in database and you want to create object using class name (e.g.: like building cache using class name and table name on startup). Or another case would be like: You get tired of calling `.add` for `BigDecimal` so you write a function to sum it up, taking an ArrayList of BigDecimal as parameter. But it's not convenient because in reality, you have object of other data type and you will have to create the ArrayList of BigDecimal by looping through the collection. This is when Reflection comes in handy. You can use Reflection to create a function, passing a collection of anything and a method name to get the BigDeicmal value out. ~~~ @SuppressWarnings({ "rawtypes", "unchecked" }) public static BigDecimal getSumBigDecimal(ArrayList bcArr, String methodName) { BigDecimal sum = BigDecimal.ZERO; if (null != bcArr && bcArr.size() != 0 && !StringUtil.isEmpty(methodName)) { try { T firstItem = bcArr.get(0); Class klass = firstItem.getClass(); Method method = klass.getMethod(methodName); for (T each : bcArr) { BigDecimal bc = (BigDecimal) method.invoke(each); sum = sum.add(bc == null ? BigDecimal.ZERO : bc); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // do what you must here, I'm catching because it's an example e.printStackTrace(); } } return sum; } } ~~~ Reflection is cool and all but please use it with care. Make sure the pros outweight the cons. *Know what you're doing.* --- --- date: "2014-09-13T00:00:00Z" tags: - Keyboard title: Poker 2 keyboard with Granite and Dolch keycaps --- I'm thinking of getting a Poker 2 keyboard and a Granite keycap set just for the sake of looking at "her". She's *so beautiful, it hurts*.
1 - Poker 2 keyboard with Granite and Dolch keycaps - credit of SpaceCadet2000
2 - KBParadise V60 Mini keyboard with Dolch DSA keycaps/RGB modifiers - credit of SpaceCadet2000
My current favorite keyboard is the Laptop Pro with the Matias Silent Switch. It's so fun to type on though I prefer the shape of the HHKB Pro 2. The downside of HHKB Pro 2 is the lack of supply for novelty keycaps. Poker 2 is the closest thing to HHKB's form factor at the moment I believe. Plus, there are plenty of novelty/custom keycaps made for MX Cherry including the beautiful, gorgeous Granite keyset above. Poker 2 is currently available for around $110 on mechanicalkeyboards.com. Used one goes for around $60. Granite keyset will cost another $120-$130, makes it a total of $240 for a new one and $190 for a used one. For reference, it costs 1 HHKB Pro 2[^hhkb-pro2] Totally worth it for such beauty IMO. [^hhkb-pro2]: *I got mine through a Japanese proxy call WhiteRabbitExpress. They quoted me $220 for the keyboard (shipping included) and $20 for their proxy service, which is very reasonable I believe.* --- --- date: "2014-09-06T00:00:00Z" description: How to force Safari to refresh website's favicon by deleting its icons cache tags: - Tip title: Force Safari to refresh website's favicon --- Force Safari to remove its website's favicon database by issuing this command in Terminal. ~~~ rm ~/Library/Safari/WebpageIcons.db ~~~ --- --- date: "2014-09-04T00:00:00Z" description: A tutorial on how to setup your VPS as a seedbox in under 15 minutes. Zero Linux knowledge required. tags: - Seedbox title: How to setup your VPS as a seedbox in under 15 mins --- *Zero (well, almost) Linux knowledge required!!!* This script is not mine. I just stumbled upon it and found it rather useful. If you are afraid about malicious script, you should download and verify it yourself. This script will setup basically everything you need for a seedbox from thin client, web ui as well as FTP. All you need to do is logging in and copy, paste these commands below, answer a few questions, then wait. ~~~ wget https://raw.githubusercontent.com/SeedboxCreator/SeedboxCreationScript/master/seedbox-setup chmod +x seedbox-setup ./seedbox-setup ~~~ Read and follow the instruction. The installation instruction is very straight forward and easy to understand. Upon finish, the setup script will also print out the information about web url, FTP address, etc... Note it down somewhere if you're not sure. *I've tested this with Ubuntu 14.04 x64 on DigitalOcean and it works flawlessly. It should work on any other VPS as well.* Looking for a VPS provider? Check [Hetzner](http://www.hetzner.de/en/) or [OVH](http://www.ovh.com) out. [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728) is also good if you're looking for a low cost VPS provider. For $15 a year, you can [setup yourself a rather decent seedbox](https://tuananh.org/2014/09/01/a-poor-mans-seedbox-for-15-dollars-a-year/). *Fig.1 - ruTorrent web ui upon finished* --- --- date: "2014-09-01T00:00:00Z" description: A Mac PreferencePane for managing services with launchd/launchctl. link: https://github.com/jimbojsb/launchrocket title: LaunchRocket --- > A Mac PreferencePane for managing services with launchd/launchctl. LaunchRocket was primarily created for managing various services installed by Homebrew, though it should work with most launchd-compatible plists. --- --- date: "2014-09-01T00:00:00Z" description: 'How to setup your own seedbox for only $15 a year. Possibly the cheapest seedbox option out there : )' tags: - Seedbox title: A poor man's seedbox for $15 a year --- Seedbox is basically just a server with torrent client installed. A seedbox usually goes for $15 a month for a decent one and $7 a month for a cheap one. It's rather easy to setup a VPS as a seedbox and save yourself some money. I have a pretty good experience with [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728). In fact, I'm running my website on one of their VPS right now. Though it's cheap but it's actually more than what I need for my static website. For $15 a year, you will get 80GB of space/500GB bandwidth monthly, 128MB RAM and 100Mbps ethernet, which is ok if you're just a casual torrent downloader like me. If you're looking for more, I suggest you take a look at [Hetzner](http://www.hetzner.de/en/) or [OVH](http://www.ovh.com). ## Get a VPS at RamNode Sign up for the massive plan (cheapest one) for $15 a year and select the geolocation closeast to your location. There are 4 datacenters that you can select from which are NYC, ATL, SEA and NL. Also, select Ubuntu 14.04 64bit as OS for the sake of this tutorial. Enter your domain if you have one. If not, you're going to access to your seedbox by IP address. I got myself one at [Atlanta](https://clientarea.ramnode.com/aff.php?aff=1728) and the speed is very decent from Thailand. RamNode will send you an email with the VPS's login information few minutes after signing up. ## Connecting to your VPS If you're on Linux or Mac, `ssh` is pre-installed and ready to use. To connect on Mac/Linux, use the command below. Enter password when asked. ~~~ ssh username@ip-address ~~~ If you're using Windows, you need to download a SSH client to connect to your VPS and set it up. [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) is a decent one. Open PuTTY, enter IP address you get from the email and click open. Enter username and password you get from the email. Now if you're panic about server's security, you can go ahead and follow [this tutorial](http://plusbryan.com/my-first-5-minutes-on-a-server-or-essential-security-for-linux-servers) to add a few extra layers. As for me, I usually just installed `fail2ban`, disable login by password, and using public key authentication only. ## Installing deluge and deluge-web I'm going to ignore all the security practices so that this tutorial can be as simple as possible. I'm assuming you're still using `root` account for now. Copy and paste the commands below, press `Y` when asked. These commands will update/upgrade to lateast package and then install `deluge`, `deluge-web`and `nginx`. Append `sudo` if needed. ~~~ apt-get update apt-get upgrade apt-get install deluged apt-get install deluge-web apt-get install nginx ~~~ `deluge` is a torrent client, `deluge-web` is deluge's web interface for easy management and `nginx` allows you to download files to your computer. *`rtorrent` is another decent torrent client (better web ui, lower memory footprint) but requiring a bit more work to setup it up and running. Also, you can setup FTP and download your files using FTP instead of direct HTTP. I choose direct HTTP simply because I have nginx server already installed so less work for me. You can also secure your public folder in nginx by following [this tutorial](http://nginxlibrary.com/password-protect-a-directory/).* Once finished, start `deluged` and `deluge-web`. ~~~ service nginx start deluged deluge-web --fork ~~~ You can access your `deluge-web` at your seedbox's IP address, default port `8112` with default password `deluge`. Now, in order to download your files from seedbox, you will change the root of nginx to your downloads folder. Your files will be accessible at `http://ip-address`. ~~~ sudo nano /etc/nginx/sites-available/default # point root to your downloads folder # root /home/username/downloads; # restart nginx afterward sudo service nginx restart ~~~ If you hit any problem, feel free to drop me an email or tweet me at `@tuananh_org`. I'll be happy to help. *Update: I added a [blog post](https://tuananh.org/2014/09/04/how-to-setup-your-vps-as-a-seedbox-in-under-15-minutes/) for those who doesn't want to tinker much with command line.* --- --- date: "2014-08-30T00:00:00Z" description: The complete, up-to-date list of all the download accelerators, managers for Mac out there. Pick your favorite. title: Download accelerators, managers for Mac --- ## [Maxel](http://maxelapp.com) - $7.99 This is my favorite download accelerator by far. I usually do a lot of torrenting on my VPS and download them later via direct HTTP. I just need some basic features of a download accelerator. Heck, `axel` was working well enough for me, except the part that I have to manually parse the link[^1] to it. Maxel offers Safari/Chrome extension (Firefox is currently in beta-testing phase) and they are quite good. You can select some text on the website and right click -> `Download selected links with Maxel`. Maxel will identify the selected text and filter out the links for you. This is what make Maxel a little better than Fat Pipe below. Maxel doesn't offer speed control but it's on checklist of the developer. ## [Fat Pipe Downloader](http://fatpipeapp.com) - Free Another decent download accelerator, Fat Pipe is very similar to Maxel above in term of features. I find it a bit unstable and the browser extension is less convenient than Maxel. Fat Pipe is currently (as of this post) [free on Mac App Store](https://itunes.apple.com/th/app/fat-pipe-downloader/id882588974?mt=12). ## [Folx](http://mac.eltima.com/download-manager.html) - $19.99 Some notable features: * Tagging system * iTunes integration * Scheduling * Speed control Offers more features than most. More expensive than most ($19.99). The app can download torrent files as well as creating it. Though, if you're using a private tracker, there's a fat chance that the app is not white-listed. None of the private trackers that I use (WhatCD, BTN, PTP, etc..) whitelist Folx. ## [SpeedTao](http://www.speedtao.net) - Free (beta) SpeedTao is currently in beta (for quite awhile now). SpeedTao also support torrent download but struggles with the same problem of private trackers as Folx. SpeedTao has an unique feature: remote download, which allows you to add transfer to a remote machine via iCloud or Dropbox. Personally, I don't really like SpeedTao because the app seems like an abandonware already. ## [iGetter](http://www.igetter.net/iGetter.html) - $25 iGetter is feature-rich, works fairly well except the interface looks like it was made in the 90s. Between iGetter and Folx, I'd say go with Folx. Call me cheap but I don't want to pay $25 for a software that was last updated over a year ago. ## Conclusion I pick Maxel simply because I don't need more features than that. Your mileage may vary. Maxel does one thing and does it very well for me. [^1]: *I wrote a small bash script to parse a file contains a list of links, new line seperated to `axel` and let it download one by one.* --- --- date: "2014-08-28T00:00:00Z" description: How to defer loading (lazy-load) stylesheet with JavaScript to speedup rendering process and improve PageSpeed score. tags: - Web performance title: How to lazy-load CSS with JavaScript --- Prioritize above the fold content and defer loading CSS can significantly improve your site's rendering speed (impression, at the very least) and PageSpeed score. Basically, you will inline those CSS rules so that content above the fold (content visible without scrolling down) can be render right away (instead of getting blocked while waiting for the whole CSS file being downloaded), making visitors feel that the site is responsive. You can use [this bookmarklet](https://gist.github.com/PaulKinlan/6284142) to identify the critical CSS rules. Note that this will not pickup the `@media` query. That, you will have to do by yourself for now. Once you've finished identifying those above-the-fold CSS rules and inline them, you will have to lazy-load the rest of the CSS with JavaScript. If you're using `jQuery`, put this in your `ready` block. Otherwise, write your own version of `ready` and put it there This is an example of `ready` in pure JavaScript. I don't need jQuery anywhere else on my site so including jQuery is too much overhead for this purpose. ~~~ (function(funcName, baseObj) { funcName = funcName || "isReady"; baseObj = baseObj || window; var readyList = []; var readyFired = false; var readyEventHandlersInstalled = false; function ready() { if (!readyFired) { readyFired = true; for (var i = 0; i < readyList.length; i++) { readyList[i].fn.call(window, readyList[i].ctx); } readyList = []; } } function readyStateChange() { if ( document.readyState === "complete" ) { ready(); } } baseObj[funcName] = function(callback, context) { if (readyFired) { setTimeout(function() {callback(context);}, 1); return; } else { readyList.push({fn: callback, ctx: context}); } if (document.readyState === "complete") { setTimeout(ready, 1); } else if (!readyEventHandlersInstalled) { if (document.addEventListener) { document.addEventListener("DOMContentLoaded", ready, false); window.addEventListener("load", ready, false); } else { document.attachEvent("onreadystatechange", readyStateChange); window.attachEvent("onload", ready); } readyEventHandlersInstalled = true; } } })("isReady", window); ~~~ Function to lazy-load CSS ~~~ function loadCSS(href){ var ss = window.document.createElement('link'), head = window.document.getElementsByTagName('head')[0]; ss.rel = 'stylesheet'; ss.href = href; // temporarily, set media to something non-matching to ensure it'll // fetch without blocking render ss.media = 'only x'; head.appendChild(ss); setTimeout( function(){ // set media back to `all` so that the stylesheet applies once it loads ss.media = 'all'; },0); } ~~~ Once ready, load the rest of the CSS ~~~ isReady(function() { loadCSS('/assets/css/main.css'); }); ~~~ --- --- date: "2014-08-27T00:00:00Z" description: Java 9 is coming with money API, read more about this API at java.net link: https://weblogs.java.net/blog/otaviojava/archive/2014/08/25/java-9-coming-money-api title: Java 9 is coming with money API --- Java provides you two exchange providers which are ECB (European Central Bank) and IMF (International Monetary Fund). You also can write our own class implementing `ExchangeRateProvider` interface. The biggest problem here is the data though, not the implementation . If you work for a bank, problem is solved. ~~~ ExchangeRateProvider imfRateProvider = MonetaryConversions .getExchangeRateProvider("IMF"); ExchangeRateProvider ecbRateProvider = MonetaryConversions .getExchangeRateProvider("ECB"); ~~~ --- --- date: "2014-08-26T00:00:00Z" description: Safari Tab Switching is a Safari SIMBL plugin which allow switching between tabs using Cmd+1-9. link: https://github.com/rs/SafariTabSwitching title: SafariTabSwitching --- > Safari Tab Switching is a Safari SIMBL plugin which allow switching between tabs using Cmd+1-9. The most annoying thing (aside the web inspector) when I switch back to Safari due to a power consumption issue with Chrome. --- --- date: "2014-08-25T00:00:00Z" description: Maxel - a native download accelerator for OS X link: http://maxelapp.com title: Maxel - a native download accelerator for OS X --- > Maxel is a download accelerator—it splits each download into parts and downloads the parts simultaneously, maximizing your bandwidth. ![maxel - download accelerator app for os x](/img/maxel.png) --- --- date: "2014-08-23T00:00:00Z" description: "" tags: - Jekyll - Plugin title: A better sitemap for jekyll --- [jekyll sitemap](https://github.com/jekyll/jekyll/blob/master/test/source/sitemap.xml) use `site.time` for `lastmod` so it doesn't really reflect the actual last modified date of the post but the last updated date of the site instead. I'm not so sure if `lastmod` attribute has much effect on Google's crawl rate/SEO to your site since it's optional but I'm a little picky even when it comes to small thing like this. Since last modified date is available from the source file, it should be generated automatically from that. [kinnetica's sitemap plugin for jekyll](https://github.com/kinnetica/jekyll-plugins) make use of `.mtime` for `lastmod` in sitemap which is the actual last modified date of the post. Clone the repo, put `sitemap_generator.rb` in your `_plugins` folder and you're all set. --- --- date: "2014-08-22T00:00:00Z" description: Compilation of jQuery tips and tricks by StackOverflow members link: http://stackoverflow.com/questions/182630/jquery-tips-and-tricks tags: - JavaScript title: jQuery Tips and Tricks --- If your daily job has to deal with jQuery, you should definitely read this. --- --- date: "2014-08-20T00:00:00Z" description: Is Mailbox (for Mac) the last email client you ever need? title: Is Mailbox (for Mac) the last email client you ever need? --- **tl;dr**: No, hardly. The much anticipated Mailbox app for Mac released its first public beta today. Too bad, it seems to me that Mailbox has failed to live up to the expectation. Mailbox was originally released as an iOS app only. The design is very simple and elegant with an unique take on swiping/snoozes features. I'm a big fan of Mailbox for iOS and I've been waiting for Mailbox to release their Mac app companion ever since Sparrow abandoned. I realize that this is just the first public beta release so there are obvious many kinks that need to work out. I just don't like the direction Mailbox for Mac is going. The iOS version is simple and I prefer it to be stay that way. When I check my emails in the morning on my phone, my workflow is like: * act on it if i can finish it within 5 minutes. * archive it there is nothing to do and no further action required. * select Later if there's action needed to be done but cannot be done rightaway. * repeat the process. On my computer, however, I expect many more features than just that. Being acquired by Dropbox earlier, I at least hope that feature like using Dropbox for attachment would be available on day 1. Ever since Sparrow introduces that feature, it's been one of my must-have feature in an email client. You have complete control over the attachment even if the email is already sent. I can even delete it if I regret after clicking send. I can even update the file without resending the email. The email composer is seriously lack of features. No markdown, no HTML, no quoting whatsoever. Just pure plaintext. Simplicity is good but it's no excuse for the lack of features. The UI looks modern but definitely not my thing. It's way too bright and the contrast is too low for my taste. And there's no preferences to customize the UI. You're stucked with the default. I will stick with Airmail for now. With some little tweaks, Airmail can look pretty decent and quite feature-complete for me. It's the closest thing to Sparrow I can find. ![airmail os x](/img/airmail.png) --- --- date: "2014-08-19T00:00:00Z" description: A simple fix for unable to download huge files with varnish (using pipe). tags: - Varnish title: Fix unable to download huge files with varnish --- I recently stumble against a problem with my varnish setup. I couldn't download any file (huge ones) from my webserver (nginx). The problem only seems to occur with varnish enabled. A little Google-fu, I found that the problem indeed was caused by varnish because varnish was intefering (timeouts and post-processes) with the request from client. The fix is simple: let client and server talk directly without varnish interfering using `pipe`. ~~~ sub vcl_recv { # condition to trigger pipe if (req.url ~ "^/path/or/file/extension/or/whatever/condition/you/have/") { return (pipe); } } ... sub vcl_pipe { set bereq.http.connection = "close"; } ~~~ Try `wget` your files again. It should be fixed now. --- --- date: "2014-08-14T00:00:00Z" description: Lift - an app that help you to be a better person than you were yesterday title: Lift - an app that put your goals into action --- [Lift](https://www.lift.do) is an app that help you to form/track/analyze new habits. This is part of my new experiment to become a better self. Some of the stuff I like to do it more often and more regular like: pushup every morning, drink more water, learn new language, talk to one stranger a day, etc.. for starters. I'm still very much in the process of learning, tweaking this method. But that's the beauty of it. Becoming a better you than yesterday is not just a habit, it's a way to improve life. --- --- date: "2014-08-13T00:00:00Z" description: Learn how to easily create a multilingual jekyll site without the need of installing any plugin tags: - Jekyll title: Localization with jekyll --- Do you want to create a multilingual blog with jekyll? Here's an easy way to setup jekyll to have content in multiple languages. ## Create localized string data file Create a folder named `_data` if you don't already have one. Inside that folder, create a localization data file and name it `messages.yml`. ~~~ mkdir _data touch messages.yml ~~~ Edit `messages.yml` and add localized strings. For the moment, I will just create a simple phrase and 2 localized strings in English and Vietnamese for testing purpose. ~~~ locales: en: example: "example" vn: example: "Ví dụ" ~~~ ## Display localized strings on post/page Now you need to capture the locale of the current page. You can do it either by capturing the url (if you organize posts by folders of language's name) or from the page variable. To make it easy in this port, i will use the current page's locale variable; if not specified then default to 'en'. {% raw %} ~~~ locale: "vn" ~~~ {% endraw %} Get the localized string with this syntax {% raw %} ~~~ {{ site.data.messages.locales[locale].example }} ~~~ {% endraw %} And that's it. Go back and add more localized strings, update templates and publish changes. Your website is now multilingual. *To make it even more visitor-friendly, you can create a separate RSS feed for each one of the language.* *Alternatively, you can use `jekyll-multiple-languages-plugin`[^1], though I'm not a big fan of using plugin when not I don't really need to.* [^1]: http://rubydoc.info/gems/jekyll-multiple-languages-plugin/1.2.5/frames --- --- date: "2014-08-11T00:00:00Z" description: The best tiling window manager for OS X, based on xmonad. tags: - Tiling window manager - macOS title: My favorite tiling window manager for OS X --- The problem of window manager has been with us ever since GUI and multitasking were introduced. Despite being considered as a most modern OS, OS X failed short when it comes to window manager, especially when compared with Linux. I've tried many so-called tiling window managers for OS X. All have failed for me so far but [Amethyst](https://github.com/ianyh/Amethyst). *To name a fews that I've tried: SizeUp, Spectacle, DoublePane, Slate, BetterSnapTool and some others... Most of them are not tiling but just simple window managers.* I've tried Amethyst before but it was not stable enough for daily usage at the time. I like the concept every much though. I later settled on SizeUp and Witch combo for window manager. They suck. But they suck the least at the time. And most importantly, it wasn't really tiling at all. ## About Amethyst > Tiling window manager for OS X similar to xmonad. Was originally written as an alternative to fjolnir's awesome xnomad but written in pure Objective-C. It's expanded to include some more features like Spaces support not reliant on fragile private APIs. A quick screencast to show basic features of Amethyst ## Getting started with Amethyst Basic usage of Amethyst is very well-covered in its documentation on GitHub. I won't go into details here. Some commonly used shortcuts are: * `mod1` - `option + shift` * `mod2` - `ctrl + option + shift` * `mod1 + space` — cycle to next layout * `mod1 + j` - focus the next window counterclockwise * `mod1 + k` - focus the next window clockwise * `mod1 + return` - swap the focused window with the main window * `mod1 + h` - shrink the main pane * `mod1 + l` - expand the main pane That're probably all you need to know to get started with Amethyst. Go figure some more when you're familiar with the new tool. All of them are configurable in `.amethyst` configuration file in your home directory. Amethyst is probably the best tiling window manager for OS X right now. It's simple, intuitive and very lightweight with a low learning curve. You don't have to learn a whole lot of new shortcuts to make the best out of Amethyst. It's currently the top app on my software recommendation list. --- --- date: "2014-08-09T00:00:00Z" description: Easiest way to setup post scheduling with jekyll using cronjob. tags: - Jekyll title: Post scheduling with jekyll --- First, update your `_config.yml` and set `future` [to false](http://jekyllrb.com/docs/configuration/). By doing this, jekyll will not publish any post with future date. ~~~ future: false ~~~ Setup a cronjob to check and rebuild the site if there're posts to be published. Since I already setup a hook on `post-receive` (trigger on receving a git push) to rebuild the site, I'm going to set a cronjob to execute this script instead of writing something new. The content of the `post-receive` looks like this. ~~~ # post-receive #!/bin/bash -l GIT_REPO=$HOME/repos/myblog.git TMP_GIT_CLONE=$HOME/tmp/git/myblog PUBLIC_WWW=/var/www/myblog git clone $GIT_REPO $TMP_GIT_CLONE jekyll build --source $TMP_GIT_CLONE --destination $PUBLIC_WWW rm -Rf $TMP_GIT_CLONE exit ~~~ Cronjob that execute `post-receive` every hour at 0 minute, everyday. ~~~ @hourly /path/to/my/post-receive ~~~ The only downside of this is you need to be able to setup cronjob. GitHub Pages and shared hostings are probably not going to work in this case. You will need to verify this yourself. --- --- date: "2014-08-09T00:00:00Z" description: The easiest way to setup Discourse local development environment on OS X tags: - Discourse - Vagrant title: How to setup Discourse local dev environment on OS X --- I find this the easiest way to install Discourse on OS X for local development purpose. The idea is this: you install Ubuntu as a VM and setup Discourse on top of that. ## Install Vagrant Setup Vagrant is as easy as double click the installer and a couple of next click. I have a license for Parallels 9 so I use Parallels as default provider. You can use VMWare if you own one or VirtualBox for free if you don't feel like paying. I use [puphpet's Ubuntu 14.04 x64](https://vagrantcloud.com/puphpet/ubuntu1404-x64) as base box but literally, anything Ubuntu 14.04 would work. Ubuntu 14.04 is recommended version by Discourse. *You can use [PuPHPet online GUI configurator](https://vagrantcloud.com/puphpet/ubuntu1404-x64) to create a configuration of your own.* ~~~ vagrant plugin install vagrant-parallels mkdir discourse-local cd discourse-local vagrant init puphpet/ubuntu1404-x64 vagrant up --provider=parallels vagrant ssh ~~~ ## Install Discourse with Docker Follow this [tutorial on Discourse Meta](https://meta.discourse.org/t/beginners-guide-to-install-discourse-on-ubuntu-for-development/14727) to setup Discourse on your Vagrant box. Discourse should be up and running on your Vagrant box's IP address after this. --- --- date: "2014-08-08T00:00:00Z" description: Adding full-text search on top of your jekyll websites without using jQuery or plugin tags: - Jekyll - Plugin title: jekyll full-text search without jQuery or plugin --- I did a quick search for full-text search solutions for jekyll (*or any static websites*). A notable fews have come up in search result: * [jekyll-lunr-js-search](https://github.com/slashdotdash/jekyll-lunr-js-search) * index_tank * [tapir](http://tapirgo.com) ## lunr-js-search A quick look at the plugin's dependency, I give up right away even though I only need that on search page. For the very same reason I opted for jekyll - a static site generator, I want my site to load as fast as possible. Adding a bunch of dependencies like that is against the purpose. I rather go for DuckDuckGo as I'm currently doing. > Dependencies for the jQuery plugin are as follows. > > * jQuery > * lunr.js > * Mustache.js > * date.format.js > * URI.js ## index_tank Too bad the service is [no longer available](https://twitter.com/indextank/status/189851390695321601). ## tapir The most decent service I found so far. You sign up, add your feed URL so that the service can start indexing your website, add jQuery and `tapir.js` and you're good to go. Search API: ~~~ api/1/search.json?token=******&query=***** /api/1/search.json?token=******&query=*****&callback=myCallback ~~~ `tapir.js` looks like a pretty short and simple js file. Adding jQuery dependency on it, is totally overkill. Exercise for next week: I'm going to replace DuckDuckGo search with Tapir's search in a few days once I'm done converting Tapir's jQuery plugin to a pure js solution. --- --- date: "2014-08-08T00:00:00Z" link: https://blog.cloudflare.com/google-now-factoring-https-support-into-ranking-cloudflare-on-track-to-make-it-free-and-easy tags: - CloudFlare title: CloudFlare to offer free-SSL by mid-October --- > We're on track to roll out SSL for all CloudFlare customers by mid-October. When we do, the number of sites that support HTTPS on the Internet will more than double. That they'll also rank a bit higher is pretty cool too. CloudFlare is the coolest free CDN option out there. They responds so quick to the new Google's [HTTPS as a ranking signal](http://googlewebmastercentral.blogspot.com/2014/08/https-as-ranking-signal.html). --- --- date: "2014-08-07T00:00:00Z" tags: - Jekyll title: Make your jekyll blog a little bit more SEO-friendly --- Content is the king but making the site a bit more SEO-friendly (or should I say Google-friendly?) doesn't hurt. ## Load speed *Relevant link: [Optimize the hell out of your website for PageSpeed](/2014/07/30/optimizing-the-hell-out-of-your-site-for-pagespeed/)*. ## Sitemap Help Google's bots crawl your site better. jekyll provides it as [a plugin.](https://github.com/jekyll/jekyll-sitemap) ## Structure data markup Use Google's [structure data markup helper](https://www.google.com/webmasters/markup-helper?hl=en) to generate structure data markup code. Use [structure data testing tool](Structured Data Testing Tool) to verify it afterward. ### SSL Google recently announced that they are taking [HTTPS as a ranking signal](http://googlewebmastercentral.blogspot.com/2014/08/https-as-ranking-signal.html). StartSSL offers free SSL that is included in root SSL of most major browers. CloudFlare is also going to offer free-SSL to all customers (mid-October). I'm going to wait for CloudFlare a bit since I'm already using CloudFlare for this blog. --- --- date: "2014-08-07T00:00:00Z" description: An attempt to create a complete jekyll plugin directory. Submit yours now. tags: - Jekyll - Plugin title: jekyll plugin directory --- [Jekyll plugin directory Wiki](https://github.com/tuananh/jekyll-plugin-directory/wiki/Jekyll-Plugin-Directory) --- --- date: "2014-08-07T00:00:00Z" title: Another attempt at regular blogging --- I believe blogging help me learn better. In order to write about a subject, first I have to understand it throughly. Blogging is a way to documentating all your thought, your understanding into words. Many people I talked with afraid of being wrong on the Internet. Don't be. I do it all the time. I learnt a lot from the feedbacks. If the feedback is right, you then learn something new, which is great. That's the whole point, right! If you're wrong, please don't get offended. A feedback could be wrong but it will also help you understand the subject better and get better at defending your view. Either way, it helps. Actually, being wrong may actually speed up the learning process. > "The best way to get the right answer on the Internet is not to ask a question, it's to post the wrong answer." > > -- [Cunningham's Law](http://meta.wikimedia.org/wiki/Cunningham%27s_Law) You will be surprised at number of people can't wait to prove you wrong. ![duty calls](http://imgs.xkcd.com/comics/duty_calls.png) --- --- date: "2014-08-06T00:00:00Z" tags: - Jekyll - Plugin title: Paginated post plugin for jekyll --- I was wondering if anyone has created one like this before and stumbled across [this issue](https://github.com/imathis/octopress/issues/912) on `octopress` repo. imathis came up with this proposition for paginated post. * The primary page would be the standard post url. * Successive pages would be at `post-url/2/index.html`, etc. * The atom feed will still show the full un-broken post. * A unified post will live at `post-url/all/index.html` containing a print-friendly, un-broken version of the post. * Pagination at the bottom will like to each page followed by a link to `post-url/all/`. * Posts will have paginate: true in their YAML header to enable this feature. * Posts will be broken up by html comments like ``. * The un-broken post will add visual page divisions, probably an `
`. * There will be a way to direct read-later services (like Instapaper) to the un-broken post page. I don't actually need this plugin as my posts are mostly short posts but I will take this as a fun exercise over this weekend or next. The plugin will be available on GitHub later. --- --- date: "2014-08-04T00:00:00Z" description: Tutorial on how to write your first jekyll plugin. tags: - Jekyll - Plugin title: Writing your first jekyll plugin --- *[jekyll documentation](http://jekyllrb.com/docs/plugins/) is actually a very good source if you want to learn about writing a jekyll plugin. I highly recommend you to read that first. There are many examples at the end of the page as well.* --- Jekyll has a plugin system with hooks that allow you to create custom generated content specific to your site. You can run custom code for your site without having to modify the Jekyll source itself. In general, plugins you make will fall into one of three categories: * Generators * Converters * Tags and liquid filters We shall try with liquid filter first today, which is the easiest one IMO. In this tutorial, we will create a plugin that convert `[x]` and `[]` into emoji icon checkbox. > Filters are simply modules that export their methods to liquid. All methods will have to take at least one parameter which represents the input of the filter. The return value will be the output of the filter. Create a file named `Checked.rb` in your `_plugins` folder with content as below. {% raw %} ~~~ module Jekyll module Checked def checked(text) text.sub(%r{\[x\]}i, '').sub(%r{\[\]}i, '') end end end Liquid::Template.register_filter(Jekyll::Checked) ~~~ {% endraw %} We just created a module with a method that take a string as param and replace `[x]` and `[]` with a `span` tag with respective class. We will then apply this filter on post content (*or anything you want to apply this filter on*). {% raw %} ~~~ {% content | checked %} ~~~ {% endraw %} In your css file: ~~~ .ballot_box_with_check:before { content: "\2611\ "; } .ballot_box:before { content: "\2610\ "; } ~~~ The result looks something like this ![check jekyll plugin](/img/checked-jekyll-plugin.png) And there you have it: your very first jekyll plugin. Be sure to check back for more jekyll tutorials :) . --- --- date: "2014-08-04T00:00:00Z" description: A trick to use any kind of plugin you want with GitHub Pages link: https://github.com/randymorris/randymorris.github.com tags: - Jekyll - Plugin title: How to use jekyll plugins with GitHub Pages --- Very cool idea by `@randymorris`. Basically, actual source will now live in the `source` branch and generated content (`_site` folder) will be in `master` instead. 1. Make changes in the source branch 2. Build and test the site locally 3. Commit changes to source branch 4. git publish-website which consists of the following steps ~~~ git branch -D master git checkout -b master git filter-branch --subdirectory-filter _site/ -f git checkout source git push --all origin ~~~ This is actually similar to the approach: develope locally and rsync `_site` folder with your VPS/host public folder but works with GitHub Pages. --- --- date: "2014-08-03T00:00:00Z" tags: - Jekyll title: Embedding Liquid code snippet with jekyll --- If you want to share some Liquid code snippet or say some Django code, jekyll would try to process it as it looks like valid Liquid template and throw some weird errors. The solution is to use `raw` tag. Again, I cannot use `raw` tag nested inside `raw` tag. Though, you would probably never have to do it, unless writing a blog post about something like this. {% raw %} ~~~ {% include analytics.html %} ~~~ {% endraw %} --- --- date: "2014-08-02T00:00:00Z" description: A jekyll plugin to generate monthly/yearly archive so you can access them via URL like example.org/2014/08/. tags: - Jekyll - Plugin title: Monthly/yearly post archive generator plugin for jekyll --- Basically, this plugin will generate a archive based on your specified layout so that you can access it at url like ~~~ example.org/2014/ example.org/2014/08/ ~~~ At first, I thought this is one of the default feature of jekyll but I was mistaken toto for jekyll. [toto](http://github.com/cloudhead/toto) was another static site generator that I used before. Anyway, this is what you are gonna get at the end.
jekyll archive plugin
1 - Example result of using jekyll archive plugin
*I'm not the author of this plugin. I just modified it to suit my needs, adding stuff like: grouping post, generate yearly archive.* If you want to take a peak, head over to the [plugin's repo on GitHub](https://github.com/tuananh/ArchiveGenerator). --- --- date: "2014-08-01T00:00:00Z" tags: - Jekyll title: Blogging with jekyll --- I started using jekyll with Github Pages few years back but Github Pages didn't allow plugins at the time and I do want to hack a bit around jekyll for my needs so I gradually move to self-hosting. As for static blog doesn't require much resource, I opted for the lowest package on [RamNode](https://clientarea.ramnode.com/aff.php?aff=1728) which goes for $15 a year (a bit over a buck a month for a 128MB RAM VPS). This is actually plenty for my needs. ## Install jekyll Install `rvm` or `rbenv`. If you already have one, skip this, then install `jekyll` ~~~ curl -L https://get.rvm.io | bash -s stable --ruby=2.0.0 gem install jekyll ~~~ Create your new blog and `git init` it. ~~~ jekyll new example.org cd example.org git init git add . git commit -m "Initial commit" jekyll serve --watch ~~~ ## Deploy I create a script on my repo `post-receive`. Upon receive my push commit, the script will rebuild the site from source, output it to nginx public folder `/usr/share/nginx/html/example.org` and purge varnish cache afterword. Purging varnish cache is optional but I do change my CSS/JS occationally now and then so I put it there as well. You can do this by following this tutorial on DigitalOcean - ["How To Deploy Jekyll Blogs with Git"](https://www.digitalocean.com/community/tutorials/how-to-deploy-jekyll-blogs-with-git). It's pretty easy to follow. Use your copy-paste-fu. *Note: If you're using Ubuntu 14.04 or later, you will have to do to `apt-get install nodejs` as well. jekyll itself doesn't depend on `nodejs` but `execjs` does.* ## Others I wrote a bunch of [posts about jekyll](/tag/jekyll/) as well. Check these out if they interest you. * [BetterTube - A jekyll plugin for embedding YouTube videos](/2014/07/31/bettertube-a-jekyll-plugin-for-embedding-youtube-videos/) * [Static assets combine with jekyll](/2014/07/29/static-asset-combine-with-jekyll/) * [Speed up jekyll site generation with rsync](/2014/07/26/speed-up-jekyll-site-generation-with-rsync/) * [Setup custom error page for jekyll on nginx](/2014/07/26/setup-custom-error-page-for-jekyll-on-nginx/) * [Create a contact form with jekyll](/2015/01/20/how-to-create-a-contact-form-with-jekyll/) * [How to group posts by month in Jekyll archive page](/2013/11/12/how-to-group-posts-by-month-in-jekyll-archive-page/) * [Link Post in Jekyll](/2013/09/02/link-post-in-jekyll/) * [Optimizing the hell out of your site for PageSpeed](/2014/07/30/optimizing-the-hell-out-of-your-site-for-pagespeed/) (*relevant*) * [Using AngularJS with jekyll](/2015/01/14/using-angularjs-with-jekyll/) * [Using GitHub issue tracker as comment system for jekyll](/2015/01/03/using-github-issue-tracker-as-comment-system-for-your-static-blog/) * [A better sitemap for jekyll](/2014/08/23/a-better-sitemap-for-jekyll/) * [Localization with jekyll](/2014/08/13/localization-with-jekyll/) * [Post scheduling with jekyll](/2014/08/09/post-scheduling-with-jekyll/) * [Monthly/yearly post archive generator for jekyll](/2014/08/02/monthly-yearly-post-archive-generator-plugin-for-jekyll/) Happy blogging. Don't forget to share your cool jekyll hack on your new hippy blog. --- --- date: "2014-07-31T00:00:00Z" description: BetterTube is a jekyll plugin for embedding YouTube videos. It's responsive and PageSpeed-friendly by making use of defer iframe loading. tags: - Jekyll - Plugin title: BetterTube - A jekyll plugin for embedding YouTube videos --- ## Sample syntax {%raw%} ~~~ {% youtube http://youtu.be/NiYCgVKioI4 %} ~~~ {%endraw%} or even shorter {% raw %} ~~~ {% youtube NiYCgVKioI4 %} ~~~ {% endraw %} Basically, this plugin will replace your youtube tag with a beautiful, responsive thumbnail of the video instead of loading the iframe and a bunch of other stuff along with that iframe, right on page load (pagespeed, remember?). On click, it will replace the image with the iframe for embedding video and play (`autoplay=1`). And did i mention it's responsive too? I will publish this plugin on Github as soon as I clean up the code. The plugin is now [available on Github](https://github.com/tuananh/BetterTube). --- --- date: "2014-07-30T00:00:00Z" tags: - Web performance title: Optimizing the hell out of your site for PageSpeed --- ## Static assets bundling I wrote a post about [static assets combine](/2014/07/29/static-asset-combine-with-jekyll/) on jekyll but the idea is similar to other framework. You can even do this on Wordpress via plugin called W3TC IIRC. As for your theme's images, use CSS sprites to combine it to a single image. There are many tools for this purpose. This step is critial because number of requests has a very high impact on page load. You would get a few points for PageSpeed already after this step. ## Make use of Content Delivery Network (CDN) CDN deploys your content across multiple, geographically dispersed servers, making your page loads faster from vistors' perspective. Also, try to load external library from a popular CDN. Chance your visitors already cache jQuery on Google CDN is pretty high. Why not take advantage of this? CDN is cheap now. Even the free option CloudFlare is pretty good. Use it. ## Minify CSS/JS/HTML This one doesn't take much time either. CloudFlare can also do it for you if you're lazy. ## Defer, async when possible Defer and async all the scripts where possible. The rules are simple * Without `defer` or `async`, HTML parsing is paused when js code executes. * Defer will delay the script execution until HTML parsing is done. * Async won't care when the script will be available. The script will be executed as soon as it's ready. You may not want to use `async` if you have other scripts depend on that. If you need to put external scripts (analytics, ads, etc..) on your site, try to find their async version. Most of the popular ads network provide async code snippet for your site. Put JavaScript which are `defer`able in the bottom before ` None of the above-the-fold content on your page could be rendered without waiting for the following resources to load. --- --- date: "2014-07-29T00:00:00Z" description: Make use of static assets combine (bundling) to reduce number of requests, making your site loads significantly faster. tags: - Jekyll title: Static assets combine with jekyll --- If you have several css files, each for different purpose, combining it into one would [minimize HTTP requests](https://developer.yahoo.com/performance/rules.html) and improve load times significantly. jekyll mades it very easy with `include` tag. Here's how you do it. - Move all the css files you want to include to `_includes` folder. - You create a new layout in `_layouts` folder called `style.html` and includes the needed css there. {% raw %} ~~~ --- layout: nil --- /* syntax css, base css, or whatever you need to include */ {% include css/style.css %} {% include css/syntax.css %} ~~~ {% endraw %} This file is basically your base css file, including all the frequently used files. - Next, create your css file like below, include it in your head tag and you're done. All your css files are now included in the layout `style` which you used as a base css for your main css. ~~~ --- layout: style --- /* Your custom css goes here */ ~~~ Alternatively, if your css file is rather small, you can embeded them right into the HTML file/layout like this. I'm not a fan of this as it would look a bit mess up, even just in the source code. ~~~ Your page title ~~~ Last step: check your PageSpeed score again and smile. --- --- date: "2014-07-28T00:00:00Z" description: Some fun (weird) stuff about JavaScript array. tags: - JavaScript title: Things you may not know about JavaScript array --- Let's begin with creating an array. ~~~ arr = [1,2,3]; typeof a; > "object" ~~~ you get `object`. Wow, so how would I check whether it is an array? Here's how people usually do it. ~~~ if ('[object Array]' === Object.prototype.toString.call(arr)) { // your code } ~~~ Next, what if I'm trying to access index that is greater than current length? ~~~ arr[10] = 10; ~~~ Seems ok!? No `index out of range` error whatsoever !? Let's take a look at it again. ~~~ > [1, 2, 3, undefined × 7, 100] ~~~ Looks like JavaScript automatically expands the array and filled the blank with `undefined`. What about if I'm trying to shrink the array ~~~ arr.length = 1; > [1] ~~~ JavaScript reduces the size and keeps only elements from 0 to new length. What if I try something like this ~~~ arr['hello'] = 100; > [1] ~~~ Hmm, where is `hello`? Let's try to access it again ~~~ arr['hello']; > 100 ~~~ Phew, it's still there. Ok, let's stop making fun about JavaScript here for now. No matter how you see it, JS is a pretty good language. I kinda the language in general but some stuff, it's just weird!! Though I have to admit, it's fun to debug. --- --- date: "2014-07-27T00:00:00Z" description: How to turn ref cursor into json object in Oracle 10G. An equivalent version of row_to_json of Postgres in Oracle 10G. link: http://ora-00001.blogspot.com/2010/02/ref-cursor-to-json.html tags: - Oracle title: Returning query result as json array with Oracle 10G --- I love Postgres SQL and one of my favorite feature is ability to return the query result as a JSON array string. ~~~ select array_to_json(array_agg(row_to_json(t))) from ( select * from my_table ) t ~~~ I wonder if I could do the same with Oracle. Too bad the Oracle version at work is a bit rusty so it's a bit more troublesome to get it work. We have to first, generate XML using `DBMS_XMLGEN` and then transform it to JSON using a [XLTS stylesheet](https://code.google.com/p/xml2json-xslt/). ~~~ function ref_cursor_to_json (p_ref_cursor in sys_refcursor, p_max_rows in number := null, p_skip_rows in number := null) return clob as l_ctx dbms_xmlgen.ctxhandle; l_num_rows pls_integer; l_xml xmltype; l_json xmltype; l_returnvalue clob; begin -- generate JSON from REF Cursor l_ctx := dbms_xmlgen.newcontext (p_ref_cursor); dbms_xmlgen.setnullhandling (l_ctx, dbms_xmlgen.empty_tag); -- for pagination if p_max_rows is not null then dbms_xmlgen.setmaxrows (l_ctx, p_max_rows); end if; if p_skip_rows is not null then dbms_xmlgen.setskiprows (l_ctx, p_skip_rows); end if; -- get the XML content l_xml := dbms_xmlgen.getxmltype (l_ctx, dbms_xmlgen.none); l_num_rows := dbms_xmlgen.getnumrowsprocessed (l_ctx); dbms_xmlgen.closecontext (l_ctx); close p_ref_cursor; if l_num_rows > 0 then -- perform the XSL transformation l_json := l_xml.transform (xmltype(get_xml_to_json_stylesheet)); l_returnvalue := l_json.getclobval(); else l_returnvalue := g_json_null_object; end if; l_returnvalue := dbms_xmlgen.convert (l_returnvalue, dbms_xmlgen.entity_decode); return l_returnvalue; end ref_cursor_to_json; ~~~ If you want to read more about this, head over to [this blog post](http://ora-00001.blogspot.com/2010/02/ref-cursor-to-json.html) on ORA-00001 blogspot. This utility is available as part of [plsql-utils](https://code.google.com/p/plsql-utils/ ) on Google Code along with many other cool PL/SQL utilities. You should check it out if you have time. --- --- date: "2014-07-27T00:00:00Z" description: How to connect to a remote Postgres database securely through SSH tunnel title: How to connect to remote Postgres DB through ssh tunnel --- Open a SSH tunnel on your local machine. Here I'm openning a tunnel on my local port 5555, mapping it to the remote 5432 default port of Postgres. *If you have already changed the default port of Postgres, change it accordingly here as well* ~~~ ssh -fNg -L 5555:localhost:5432 username@host ~~~ I'm going to use `pgAdmin 3` app to connect to Postgres db. Just follow the new connection instruction as you're connecting a local database. If you're using IDENT authentication, your local username has to be matched with the username you're trying to log on to. Either switch to PASSWORD authentication or create a new username on remote db that match your local username as below and you're good to go. ~~~ CREATE USER myuser; ALTER USER myuser WITH PASSWORD 'password'; ALTER USER myuser WITH CREATEUSER CREATEDB; ~~~ --- --- date: "2014-07-26T00:00:00Z" description: A little trick to help speeding up jekyll site generation process by skipping certain static files during generation and sync using rsync instead. tags: - Jekyll title: Speed up jekyll site generation with rsync --- Jekyll doesn't support incremental generation so if your site contains loads of static assets like image, audios,.. jekyll is gonna stroke everytime you re-generate your site by deleting and re-generate/re-coppying everything. This little trick below will help you speeding up jekyll generation process by skipping all the static assets that you specified, until jekyll comes up with a better solution. * Rename your static asset folder with underscore in front. jekyll is going to ignore this when generating site. Assume this folder is named `images`. You will rename it to be `_images`. * In your `_config.yml`, add the line below. This line basically means that when re-generating, the folder called `images` in generated folder is not getting touched. ~~~ keep_files: ["images"] ~~~ * Keeping in the source and generated folder in sync with a little plugin and rsync. Created a file name `rsync_image_generator.rb` in `_plugins` folder with the content below. ~~~ module Jekyll class RsyncImageGenerator < Generator def generate(site) system('mkdir -p _site'); system('rsync --archive --delete _img/ _site/img/'); end end end ~~~ From now on, jekyll will ignore the `_images` folder in your source and `images` in `_sites` folder when generating your site. We keep it in sync by using rsync, which is much better/faster than delete/copy process jekyll is using. *Github Pages does support jekyll gems now but it's kinda limited.* --- --- date: "2014-07-26T00:00:00Z" description: How to setup custom error page for jekyll on nginx. tags: - Jekyll title: Setup custom error page for jekyll on nginx --- First of all, create your custom error page. You can either use static html or jekyll template for this. If you decide to use one of your custom layout, the path will be a bit different when jekyll generates static pages. I created a `404.html` file that looks like this. ~~~ --- layout: default title: "404: Page not found" --- Olps. ~~~ Now if you create one like mine above at root, it will be generated as `404/index.html` so use this path instead of just `404.html` when configure nginx. Edit your nginx config file to add these lines inside server block. ~~~ error_page 404 /404/index.html; error_page 500 502 503 504 /50x/index.html; ~~~ Test config your new nginx conf, reload (or restart) and you're good to go. --- --- date: "2014-07-05T00:00:00Z" link: https://github.com/mjolnir-io/mjolnir title: Hydra - an open-source window manager for OS X written in Lua --- > Open-source, written in Lua Gotta try this. *Update: Hydra was renamed to mjolnir due to trademark issues* --- --- date: "2014-06-22T00:00:00Z" tags: - Programming title: Clean code/dirty code --- I like to think of myself as a perfectionist. I like going back and forth re- evaluate all the possible options and come up with the cleanest, best, perfect alternative (I can think of) out there. If you are like me and you got to maintain someone else's bad code, this is gonna be a real nightmare. You just want to scratch everything and start over again. Unfortunately, management people doesn't think so and you don't get the luxury of times you need. All they want is you get it fixed and ship the product as soon as possible. I thought it is what makes me a better developer but in the end, getting a shippable product is what matters. Sometimes, it's just better to use a bit of dirty, tricky code to make it works. Sometimes, you just need a bit use of dirty code if it is what it takes to get the product out of the door. --- --- date: "2014-05-28T00:00:00Z" title: How to use SSH tunnel to access blocked websites --- ### Requirements * An UNIX server/VPS (since it's cheaper than Windows server/VPS). Lowest package would probably do. Location: not in the the same area/country as you. * A little copy/paste skill. No Unix commands knowledge required. ### Howto * If you're on Unix/OS X, open Terminal app and issue the command ~~~ ssh -D 1080 username@ip_or_domain ~~~ This command will connect to your server/VPS using ssh and open a ssh tunnel on port 1080 of your computer. Setup your browser/OS to use `localhost:1080` as socks5. All the traffic will be sent over ssh tunnel hence your ISP will only see it as you're accessing your server/VPS. *If you're on Windows, you can grab Tunnelier (free). The setup is pretty straight forward.* --- --- date: "2014-05-11T00:00:00Z" tags: - Programming title: How to code like a dick in JS --- The goal of this post is to make simple things look complicated. ### 1. Invert logic Let's start with something easy. Instead of ~~~ if (x && y) { ... } // easy ~~~ Write this instead ~~~ if (!(!x || !y)) { ... } // good enough ~~~ ### 2. Extended unicode chars for variable names Use of extended unicode chars, especially those that are not visible in console like `\r`, `\t`, etc... ### 3. Use `=` instead of `==` ~~~ if (x=true) { ... } // always executed ~~~ Do this when you want to always excute that code block. Someone may see this and will attempt to "fix" it on their own, changing the original logic. Goodluck debugging it later : ) ### 4. Use 8-base or n-base instead of decimal base Because why not? ~~~ var x = 19; // 19 var y = 019; // 17 ~~~ --- --- date: "2014-05-10T00:00:00Z" title: Programming is like writing a book --- > Programming is like writing a book... except if you miss out a single comma, the whole thing makes no damn sense. On why programming is hard. --- --- date: "2014-05-10T00:00:00Z" description: A short review of the Matias Laptop Pro, a wireless mechanical keyboard by Matias. tags: - Keyboard title: Matias Laptop Pro review --- I love my HHKB. I love its form factor. It's perfect, except that it requires a mini-USB cable to connect to my Macbook which isn't very convenient. The Matias Laptop Pro is the perfect addition to my keyboards family: it's wireless, small and handy. ![Matias Laptop Pro](http://i.imgur.com/JWmx3aL.jpg) ### Design Let's take a closer look at the Matias Laptop Pro. There isn't much change to the case compare with other Matias keyboards. It's quite solid but the the feeling is not as good as the HHKB or Filco. The case is made from Injected Policarbonate (used in football helmets) which is more resistant than other commonly used, cheap ABS plastic. It's bit too glossy for my taste and a horrible fingerprint magnet (it's not deal breaker to me though). The Laptop Pro is a bit bigger in size, much less handy and quite heavier when compare with the HHKB Pro 2. It's the smallest form Matias is offering for now but I heard they mentioned about [working on a 60% board](http://www.reddit.com/r/MechanicalKeyboards/comments/1wknkm/i_am_edgar_matias_designer_of_matias_keyboards/cf2z19g). The keyboard has 2 rubber feet but still a bit slippy and easily get slided on the table. ### Switches It's very quiet. Much quieter than any switches I've ever owned or tried before. The tactile bump is quite nice but not as noticable as the Tactile Pro. This is a trade off for being quiet I guess. I've tried Brown MX keyboards and I have to say Matias quiet switches are definitely quieter, even if you do bottoming out everytime. ### Layout A bit hard for me as I get used to the HHKB layout with the `Ctrl` key at the `Caps lock` position instead. I may have to reassign the key as I keep mistakenly holding `Caps lock + Tab` for tab switching in Chrome. Also, the `Fn` key next to arrow keys choice is an interesting choice, though I do not need to use `Fn` as often as I do to on my old HHKB. ### Keycaps Unfortunately I haven't found a single place that sell custom keycaps for Matias quite switches yet. You're stucked with the default one.
If you are already spoiled by the mechanical keyboards and don't want to settle for less when you're on the go, this keyboard is for you. It's small, quiet, lightweight enough to carry around plus no more cable, thanks to the Bluetooth connection. * A side note: I expect the keyboard to continue functioning if the charging cable is used and Bluetooth is off. Well, it doesn't. I don't know about the technical difficulty to make it work though.* --- --- date: "2014-05-10T00:00:00Z" link: https://www.cakebrew.com title: Cakebrew --- ![](http://i.imgur.com/wcj8Seu.png) > A Mac app for Homebrew Not sure who're the target users for this app as most of those who want to tinker with Homebrew formulas are probably not afraid of learning a few terminal commnads. Still useful nonetheseless. --- --- date: "2014-05-02T00:00:00Z" link: https://github.com/discourse/discourse/blob/master/docs/INSTALL-digital-ocean.md title: How to install Discourse on DigitalOcean --- Zero knowledge about Ruby, Rails or Linux required. --- --- date: "2014-04-24T00:00:00Z" title: Tự do ngôn luận --- > Con trai hỏi bố: - Bố ơi, tự do ngôn luận là gì hả bố? > > Bố: - À, là ngay cả 1 thằng ngốc cũng được quyền phát biểu con à. > > Con trai: - Vậy không có tự do ngôn luận là sao? > > Bố: - Là nơi đó chỉ có những thằng ngốc mới có quyền nói. > > Con trai: - ??? Rất đơn giản và dễ hiểu phải không? :) --- --- date: "2014-03-30T00:00:00Z" title: Be happy --- [Elizabeth Smart](http://www.npr.org/2013/10/08/230204193/elizabeth-smart-says-kidnapper-was-a-master-at-manipulation) has the kind of fame no one would want: In the summer of 2002, at the age of 14, she became one of the nation's most famous kidnap victims when she was abducted from her bedroom in Salt Lake City, where she lived with her devout Mormon family. Smart was raped by him nearly every day, sometimes several times a day. The only other person in their camp was Mitchell's wife, Wanda Barzee, who treated Smart like her slave She's now married, finishing work on her degree at Brigham Young University and the head of the Elizabeth Smart Foundation. When she was asked about the best advice her mother gave her, she said: > My mom said to me, "Elizabeth, what this man has done to you, it's terrible, there aren't words strong enough to describe how wicked and evil he is. He has stolen nine months of your life from you that you will never get back. But the best punishment you could ever give him is to be happy, is to move forward with your life and to do exactly what you want to do. ... The best thing you can do is move forward because by feeling sorry for yourself and holding on to what's happened, that's only allowing him more power and more control over your life, and he doesn't deserve another second. So be happy." ... I'm not perfect at following her advice ... but I do try to follow it every day. Be happy! --- --- date: "2014-02-16T00:00:00Z" tags: - Keyboard title: Matias Quiet Pro black matte case mod --- The thing I dislike the most about Matias keyboards is the glossy, fingerprint-magnetted case. Now, just imagine the [Matias Laptop Pro](http://matias.ca/laptoppro/mac/) with black matte case. That's the dream. I would buy that in a heartbeat. ![](http://i.imgur.com/dZWqVN1.jpg) ![](http://i.imgur.com/1fA2jKt.jpg) ![](http://i.imgur.com/wipcyOS.jpg) *[Image courtesy](http://www.flickr.com/photos/88005189@N05/sets/72157631688113659/) of Aaron Frost* --- --- date: "2014-02-14T00:00:00Z" title: My must-have Cydia tweaks/apps --- I thought iOS 7 has spoiled me to the point I don't think I would need to jailbreak. I mean, there're now Notification Center and Control Center which is a much better improvement (even though Control Center is not customizable). Turns out I was wrong. A few weeks with Cydia and I have found quite a few awesome apps/tweaks. Here're some on the top of my must-have list. ## f.lux Have you ever beem waken up by your phone in the middle of the night and got blinded by how bright the screen was? Show your eyes some love and get this app. ## SwipeSelection It's awesome. Text selection is so much easier with this tweak. There's a Pro version that add [several settings](http://i.imgur.com/Kldd3XN.png) which you may like. If you don't feel like paying, `SwipeSelection` is always free. Alternatively, there is `SwipeShiftCaret` (_free_) but you have to swipe on the text edit area instead of swiping on keyboard. ## BytaFont 2 This app allows users to change the font system-wide. I personally in love with `Source Sans Pro` font. ## Activator Allows user to map many gestures for various different actions, toggles. ## BiteSMS Quick compose (with Automator), quick reply, theme, schedules, etc... ## NoSlowAnimations Animations are cool (at first). After awhile, it's more of annoying than cool. I use this app to reduce the delay to 0.6 (default 1.0. 0 is instant) and it feels much more responsive. I will add when i find more. `#HappyJailbreaking` --- --- date: "2014-01-11T00:00:00Z" link: http://matias.ca/ergopro/pc/ tags: - Keyboard title: Matias Ergo Pro --- Want an ergonomic mechanical keyboard? This is about as good as it gets. _Matias has long been known for its excellent Matias Tactile Pro keyboard line and recently Matias Quiet Pro. I owned a Matias Tactile Pro 4 before and even though I later settled on the Happy Hacking Keyboard Pro 2, I still love the feeling of Matias ALPS switches._ ![Matias Ergo Pro keyboard](http://i.imgur.com/su01qTW.jpg) --- --- date: "2013-12-19T00:00:00Z" link: http://gasmaskkeycap.com tags: - Keyboard title: GasMask keycap --- Rather nice and (most importantly) not overpriced like ClickClack keycaps. Just grabbed one myself. ![gasmask keycap topre](http://i.imgur.com/zRwF6WH.jpg) --- --- date: "2013-12-09T00:00:00Z" title: Features vs. Benefits --- ![](http://i.imgur.com/alyggP0.png) When you're trying to win customers, are you listing the attributes of the flower or describing how awesome it is to throw fireballs? --- --- date: "2013-12-05T00:00:00Z" title: Phân tâm --- Dạo này tôi chủ động không đọc tin tức buổi sáng, mặc dù có thói quen này từ rất lâu rồi. Việc đọc báo cũng giống như 1 dạng nghiện thuốc vậy. Như trước đây, không đọc báo 1 ngày là cảm giác như là tôi đang bỏ qua rất nhiều chuyện đang xảy ra. Nhưng ... thực ra việc đọc báo 1 cách ngấu nghiến mới chính là cách làm cho tôi tốn thời gian vào những việc chẳng liên quan gì tới cuộc sống của tôi. Tôi thường hay cười những bài viết như "Diễn viên X lộ hàng", "Dân mạng xôn xao XXX", ... là câu khách, giật tít. Nhưng tôi thỉnh thoảng vẫn nhấn vào những bài viết dạng đó thôi. Rốt cuộc, muốn các tờ báo đó thay đổi, chính tôi phải thay đổi trước đã. --- Bật notifications trên điện thoại cũng vậy. Thay vì mỗi lần nghe/thấy có notifications, tôi đều cầm điện thoại lên ngay lập tức để kiểm tra. Việc này chẳng mang lại lợi ích gì mà chỉ khiến bản thân mất tập trung và giảm chất lượng cuộc sống mà thôi. Việc tắt notifications đi khiến cuộc sống cảm giác nhẹ nhàng hơn rất nhiều. Candy Crush đã có thể chơi tiếp ư? Chờ nhé. Có người nhắn tin cho tôi trên Facebook? Chờ nhé. Cuộc sống thực sự có nhiều điều đáng ưu tiên hơn rất nhiều. Nếu việc đó thực sự quan trọng, họ sẽ liên lạc trực tiếp bằng điện thoại thay vì nhắn tin Facebook hoặc email cho tôi. Nếu họ không có số điện thoại của tôi thì họ cũng không thực sự quan trọng lắm trong cuộc sống của tôi và vẫn phải xếp hàng chờ như thường. --- --- date: "2013-12-05T00:00:00Z" link: http://www.anandtech.com/show/7517/google-nexus-5-review/ title: Google Nexus 5 review --- ![Google Nexus 5](http://i.imgur.com/pmUNhLD.jpg) If I wasn't getting an iPhone 5S, I would definitely buy this phone. It's a beautiful device and Android is getting so much better since last I tried (Nexus S). Takes Google Now as an example. It is so good, too good that makes Siri look dumb. --- --- date: "2013-12-04T00:00:00Z" link: http://blog.jetbrains.com/idea/2013/12/intellij-idea-13-is-released-work-miracles-in-java-and-beyond/ title: IntelliJ IDEA 13 released --- IntelliJ IDEA is the reason I'm in love with Java again. Yeah, it's that awesome!!! --- --- date: "2013-11-30T00:00:00Z" link: http://www.codinghorror.com/blog/2004/10/who-needs-stored-procedures-anyways.html title: Who Needs Stored Procedures, Anyways? (2004 post) --- Good read. I personally dislike the idea of using stored procedures as it mixes the data and logic. The performance gains (!?) is not good enough. --- --- date: "2013-11-30T00:00:00Z" title: If humans evolved from monkeys, then why are there still monkeys? --- Have you ever wonder about this when you was a kid? I did. Someone made a very [nice and simple explaination here](http://i.imgur.com/ySGQk7P.png). / *Huge image alert.* --- --- date: "2013-11-27T00:00:00Z" link: http://blog.jetbrains.com/idea/2013/11/intellij-idea-13-rc2-is-out-with-a-preview-of-new-java-7-bundled-mac-installer/ title: IntelliJ IDEA 13 to be released with Java 7 Bundled Mac Installer --- ![Good news, everyone!](http://i.imgur.com/j9EtMrH.png) > Most notably, it includes a preview version of the new Mac installer with bundled JRE 1.7. --- --- date: "2013-11-21T00:00:00Z" title: How to use Source Code Pro font with JetBrains IDE --- Source Code Pro is a very nice font but the lack of *italic* variant makes it unusable (out of the box) in any of JetBrains products. In order to workaround this, you can rather: * Copy Source Code Pro's ttf fonts to folder `/Library/Java/Home/lib/fonts` (OS X Mavericks) according to [this comment on GitHub](https://github.com/adobe/source-code-pro/issues/6#issuecomment-31901709). It works fine for me. Apparently, by doing this, IntelliJ will pick the regular variant and use it where italic is needed. So it's kinda of the same as the method below [^intellij-idea]. * Use a fall-back font that that has italic variant and looks similar to your font of choice . But if that's the case, why not just use the fall-back font as the primary one. * Don't use italic font [^intellij-idea]. * Pray and wait for someone to [submit a pull request](https://github.com/adobe/source-code-pro) of italic variant. I myself went with option 3 and decided to stick with Panic Sans in IntelliJ IDEA (and Source Code Pro everywhere else) for the time being. [^intellij-idea]: [http://i.imgur.com/bGZPdVx.png](http://i.imgur.com/bGZPdVx.png) --- --- date: "2013-11-17T00:00:00Z" link: http://www.oracle.com/technetwork/java/jvmls2013vitek-2013524.pdf title: FastR - an implementation of R in Java --- Experiment result is significantly better. *On a side note, I found [Julia lang](http://julialang.org) quite a tempting alternative.* --- --- date: "2013-11-13T00:00:00Z" link: http://stackoverflow.com/questions/1015813/what-goes-into-the-controller-in-mvc title: What goes into what in MVC --- Explain MVC like I'm 5. View: "Hey, controller, the user just told me he wants item 4 deleted." Controller: "Hmm, having checked his credentials, he is allowed to do that... Hey, model, I want you to get item 4 and do whatever you do to delete it." Model: "Item 4... got it. It's deleted. Back to you, Controller." Controller: "Here, I'll collect the new set of data. Back to you, view." View: "Cool, I'll show the new set to the user now." --- --- date: "2013-11-12T00:00:00Z" tags: - Jekyll title: How to group posts by month in Jekyll archive page --- Take a look at the [archive page of this site](/archive) to get a peek of how it looks. I posted on [Github Gist here](https://gist.github.com/tuananh/7432553) just in case anyone needs it. --- --- date: "2013-11-11T00:00:00Z" link: http://www.willa.me/2013/11/the-six-most-common-species-of-code.html title: The six most common species of code --- ![The six most common species of code](http://i.imgur.com/BnP5hlp.png) --- --- date: "2013-11-10T00:00:00Z" link: http://andhyde.com title: Hyde --- An elegant, open source and mobile first theme for [jekyll](http://jekyllrb.com) by Mark Otto - creator of Bootstrap. ![Hyde - Jekyll theme](http://i.imgur.com/lSIJbUm.png) --- --- date: "2013-11-10T00:00:00Z" link: http://webaim.org/blog/user-agent-string-history/ title: History of the browser user-agent string --- Good read. > And then Google built Chrome, and Chrome used Webkit, and it was like Safari, and wanted pages built for Safari, and so pretended to be Safari. And thus Chrome used WebKit, and pretended to be Safari, and WebKit pretended to be KHTML, and KHTML pretended to be Gecko, and all browsers pretended to be Mozilla, and Chrome called itself Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13, and the user agent string was a complete mess, and near useless, and everyone pretended to be everyone else, and confusion abounded. --- --- date: "2013-11-09T00:00:00Z" link: http://neopythonic.blogspot.com/2013/10/letter-to-young-programmer.html title: Letter to a young programmer --- *From Guido van Rossum - inventor of Python.* > I heard you enjoy a certain programming language named Python. Programming is a wonderful activity. I am a little jealous that you have access to computers at your age; when I grew up I didn't even know what a computer was! I was an electronics hobbyist though, and my big dream was to build my own electronic calculator from discrete components. I never did do that, but I did build several digital clocks, and it was amazing to build something that complex and see it work. I hope you dream big too -- programmers can make computers (and robots!) do amazing things, and this is a great time to become a programmer. Just imagine how much faster computers will be in five or ten years, and what you will be able to do with your skills then! --- --- date: "2013-11-09T00:00:00Z" link: https://chrome.google.com/webstore/detail/humble-new-tab-page/mfgdmpfihlmdekaclngibpjhdebndhdj title: Humble New Tab Page --- The latest addition to my Chrome extensions list, replaced the good old '[Empty New Tab Page](https://chrome.google.com/webstore/detail/empty-new-tab-page/dpjamkmjmigaoobjbekmfgabipmfilij)'. Humble New Tab Page is lightweight, fast, [open-sourced](https://github.com/quodroc/HumbleNewTabPage) and very customizable. --- --- date: "2013-11-03T00:00:00Z" link: https://pinboard.in/resources/extensions/ title: Pinboard tab sets --- I didn't know this feature exist until I install [Pinboard's official Chrome extension](https://pinboard.in/resources/extensions/) today. Basically, it will save all current open tabs as a tab set so you can reopen everything later. Very useful when you need to stop in the middle of doing research or stuff-alike. *Tabs in tab set will not be added to your bookmarks* --- --- date: "2013-11-01T00:00:00Z" title: Snip - another screenshot capture/annotation tool --- Features: capture scrolling window (e.g. webpages), annotate screenshot, Retina-ready and a bunch of Chinese-users-oriented features. *Free on [Mac App Store](http://itunes.apple.com/cn/app/snip/id512505421?mt=12) or [direct download from QQ website](http://snip.qq.com)*. --- --- date: "2013-11-01T00:00:00Z" title: Nexus 5 and Android KitKat --- ![](http://i.imgur.com/W6owPMM.png) Beautiful. --- --- date: "2013-10-30T00:00:00Z" title: Free OCR solution on OS X --- Tesseract OCR is probably the best open source OCR engine available. It allows you to convert text from an image. ## Install tesseract I suppose you already have homebrew installed. If not, copy and paste this into Terminal. ~~~ ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)" ~~~ Once you got homebrew, installing tesseract is as simple as `brew install tesseract` ## Automate the process `tesseract` accepts input as image. If you input is PDF, you will have to convert it to image first, perferably TIFF format. You probably will want to automate this process with Automator. Fire up Automator and create a new application. ![](http://i.imgur.com/fAd0Xxx.png) Search for the action "Render PDF Pages as Images" and drag it to the right. Change the format to TIFF and increase DPI to 300 (*The higher, the better accuracy rate*) ![](http://i.imgur.com/OFS1xLI.png) Search and drag action "Rename Finder Items" to the workflow on the right and change it as the following image. Remember to tick "Make all number 4 digits long" just to ensure the numbering is right if you got lots of pages in the PDF file. ![](http://i.imgur.com/u5UrpEs.png) Add action "Move Finder Items" to the workflow. Select your desired folder as you see fit. ![](http://i.imgur.com/ArXQ77c.png) Now, this is the part where where OCR begins. We will convert each image to text by passing each image to tesseract. We then will append the output to our `output.txt` file. Remember to change `tesseract` path according to your tesseract version number. (*It's 3.02.02 as of this post*) The last step in the script is removing the images and the buffered text files. It's optional. ~~~ rm ~/Desktop/out.txt for f in "$@" do /usr/local/Cellar/tesseract/3.02.02/bin/tesseract "$f" "$f" cat "$f.txt" >> ~/Desktop/out.txt rm "$f" "$f.txt" done ~~~ The last step looks like this in Automator ![](http://i.imgur.com/tHYc6V2.png) Save this Automator workflow to Desktop or wherever you want. Next time you need to OCR a PDF document, just drag and drop it over the application's icon. The output text will be saved as `output.txt` on Desktop. --- --- date: "2013-10-30T00:00:00Z" link: http://flamingo.im title: Flamingo for Mac --- Beautiful, modern chat client for Mac from Indragie Karunaratne - creator of Sonora. Flamingo supports Google Hangouts, Facebook and other XMPP services with many cool features such as: inline media, send file via Droplr/CloudApp and an unified contact lists. ![](http://i.imgur.com/qAf66k7.png) --- --- date: "2013-10-23T00:00:00Z" link: https://github.com/ianyh/Amethyst title: Amethyst - A tiling window manager for OS X --- > Tiling window manager for OS X similar to xmonad. Was originally written as an alternative to [fjolnir](https://github.com/fjolnir)'s awesome [xnomad](https://github.com/fjolnir/xnomad) but written in pure Objective-C. It's expanded to include some more features like Spaces support not reliant on fragile private APIs. Simply the best tiling window manager for OS X I've found. --- --- date: "2013-10-20T00:00:00Z" link: https://discussions.apple.com/message/18979708#18979708 title: Fixing Mac App Store error 100 when purchasing app --- Follow the instructions above and I got it fixed. No idea why it happened in the first place. To save you sometimes, just copy and paste those lines below in Terminal. Logout, login back and try purchasing an app again. ~~~ rm ~/Library/Caches/com.apple.appstore/Cache.db; rm ~/Library/Preferences/com.apple.appstore.plist; rm ~/Library/Preferences/com.apple.storeagent.plist; rm ~/Library/Cookies/com.apple.appstore.plist ~~~ --- --- date: "2013-10-05T00:00:00Z" link: http://facemath.tumblr.com/ title: FACEMATH --- An awesome Tumblr to follow. Here's one: ![facemath](http://i.imgur.com/DNNTtJY.jpg) --- --- date: "2013-10-04T00:00:00Z" link: http://pilotmoon.com/dropshelf/ title: Dropshelf --- An alternative to Yoink and DragonDrop. Yoink is still a better choice IMO; however Dropshelf has few cool features that are not available in Yoink such as multiple shelves; dragging to any edges. Yoink has a great feature which I don't know why none of the like-apps haven't considered adding: "move window to mouse location where drag is initiated". It helps a lot in reducing mouse cursor movement. The last alternative, DragonDrop, on the other hand, is very buggy and use a rather 'inconvinient' method (shake the mouse cursor) for displaying the drop window. The last update of DragonDrop was almost a year ago (06 August 2012). I would not recommend you to buy this. Use Yoink or Dropshelf instead. [Direct Mac App Store link - $4.99.](https://itunes.apple.com/us/app/dropshelf/id540404405?mt=12) --- --- date: "2013-09-26T00:00:00Z" link: http://www.filebot.net title: FileBot --- > FileBot is the ultimate tool for organizing and renaming your movies, tv shows or anime, and music well as downloading subtitles and artwork. It's smart and just works. Require Java though. --- --- date: "2013-09-24T00:00:00Z" link: http://insights.qunb.com/why-we-hate-infographics-and-why-you-should title: Why We Hate Infographics And Why You Should --- Good read. Here's an example of why. ![](http://i.imgur.com/UiS39Cc.gif) --- --- date: "2013-09-21T00:00:00Z" link: http://www.liquid.info title: Liquid --- ![Liquid for OS X](http://i.imgur.com/Vp8PPUk.gif) It's like a less polished, less customizable version of [PopClip](http://pilotmoon.com/popclip). Also less-annoying since it's activated by hotkey instead of by selecting text. PopClip should seriously consider adding this option. *P.S.: The website maps `Command+F` on OS X to Facebook Share. Pretty weird choice of shortcut.* --- --- date: "2013-09-12T00:00:00Z" link: http://minbox.com title: Minbox --- ![](http://i.imgur.com/nec2sZP.jpg) Drag and drop to share file. No limits of file size, file type whatsoever. And the [demo video is just so gooood](http://www.youtube.com/watch?feature=player_embedded&v=QKw2qlqAAW8). Watch it. --- --- date: "2013-09-11T00:00:00Z" link: http://volumescroll.com title: Volume Scroll --- > Use your scroll over the menu bar and change the volume. *Free.* --- --- date: "2013-09-09T00:00:00Z" link: https://code.google.com/p/namebench/ title: namebench --- ![namebench](http://i.imgur.com/cPsZmiN.png) Find the fastest DNS server available for you. --- --- date: "2013-09-07T00:00:00Z" link: http://contextsformac.com/ title: Contexts for Mac --- ![contexts for mac](http://i.imgur.com/JbpkQVw.png) Currently testing this app as I'm not quite happy with Witch (unresponsive somtimes and doesn't seem to get fixed anytime soon). The app and the website look very nice though. You should check it out. --- --- date: "2013-09-03T00:00:00Z" title: Droplr Draw --- ![Droplr Draw](http://i.imgur.com/DULVehb.png) Nice and clean. *Available for Pro users only*. It's still not tempted enough to get me subscribe for Pro plan though. [Glui](http://glui.me/) is currently doing the job quite well. --- --- date: "2013-09-02T00:00:00Z" tags: - Jekyll title: Link Post in Jekyll --- Link post is nothing but the post's title linking to another url instead of the post url. You need to specify where it points to. I use `link` here but you can just use any name you feel like using. And then in `post.html` layout and `index.html` homepage. You just need to check if `link` is set; change the title's href to the `link` variable of the post. You may need to change `page` variable to `post` when looping inside your `index.html` homepage. --- --- date: "2013-08-22T00:00:00Z" link: http://nginx.com/products/ title: Nginx Plus --- I myself would love to see nginxorg to go into another direction, like CloudFlare is currently doing, taking advantages of their expertise in serving http and stuff. That way, Nginx Plus will be more of an addon service, a complement to nginxorg instead of an alternative. _On the other hand, I'm always tempted at checking out tengine for its dynamic module loading support. I now have one more reason to do so._ --- --- date: "2013-08-08T00:00:00Z" link: http://aki-null.net/shiori/ title: Shiori - A Pinboard & Delicious Client for OS X --- Goodbye Delibar. *Shiori was developed by Akhiro Noguchi - the developer of YoruFukurou.* --- --- date: "2013-08-07T00:00:00Z" link: http://www.theverge.com/2013/8/7/4596898/korea-wireless-charging-buses-kaist-olev title: Korean Buses Now Wirelessly Charge as They Drive --- > specially adapted roads with electrical cables lying just underneath the surface to magnetically transfer power to Online Electric Vehicle Imagine a future where you never have to manually charge your phone. Awesome! --- --- date: "2013-08-02T00:00:00Z" link: http://daringfireball.net/linked/2013/08/02/retina-v-battery title: Magic --- > We demand magic — a retina iPad Mini with no decrease in battery life, but no increase in thickness, weight, or price. And they need to produce at least 20 million of them by Christmas. But ... but ... it's Apple. --- --- date: "2013-07-31T00:00:00Z" link: http://phoboslab.org/ztype/ title: Z-Type --- Because all the cool kids are playing Z-Type. You know! --- --- date: "2013-07-26T00:00:00Z" link: http://www.youtube.com/watch?v=nw9g9OVHdJI title: How Koreans Solve Their Parking Issue and Saving Oil --- Brilliant idea: using balloon as signal for available parking slot. --- --- date: "2013-07-24T00:00:00Z" link: http://blogs.perl.org/users/ovid/2013/07/how-to-fake-database-design.html title: How To Fake Database Design --- In short: * Every "noun" gets its own table. * "many to many" relationship get their own tables * "one to many" relationship require the table that "owns" another table have its id in that other table * Any time a table has an id that refers to a row in another table, use a foreign key constraint to make sure that id really exists Doing this would probably results in many tables but it would help ensuring the 3NF(every non-key attrib must provide a fact about key, the whole key and nothing but the key) quite easily and go further for 4NF and 5NF - the ultimate, super-uber normal form (Jeez who need 5NF!?). If you want an easy, effortless and *good enough* design, go for this. --- --- date: "2013-07-21T00:00:00Z" link: https://github.com/phinze/homebrew-cask title: homebrew-cask --- > brew-cask provides a friendly homebrew-style CLI workflow for the administration of Mac applications distributed as binaries. It's homebrew for GUI apps. Apps via homebrew-cask get installed into `/opt/homebrew-cask/Caskroom/` and symlinked to `/Applications/` so it works just like any other apps. To use this ~~~ $ brew tap phinze/homebrew-cask $ brew install brew-cask $ brew cask install google-chrome ~~~ --- --- date: "2013-07-20T00:00:00Z" link: https://github.com/mdo/mdo-df-css title: Daring Fireball User Custom Style --- It looks much better this way. ![](http://i.imgur.com/pki7OGE.png) --- --- date: "2013-07-19T00:00:00Z" title: Trolling Or Just Pure Stupidity!? --- Saw this in [TM2's mailing list](http://lists.macromates.com/textmate/2013-July/036389.html) early this morning. > Since TM 2 is Open Sourced, so why we (unregistered non-commercial users) need to pay for the license keys? > I do not remember paying for the license keys for any Open Sourced program in the last decade. > It seems to me that the Open Source tag is a ploy to 'attract' developers, however, keeping the revenue to the owner himself. This guy has a wrong fundamental understanding about the word `free`. And as Allan has already pointed him at [GNU philosophy](http://www.gnu.org/philosophy/selling.html) but he seems to ignore and continue trolling everyone in the mailing list. And the best part is, he's not just some undergrad. This guy is doing his PhD thesis using TM2!! I can't help myself but feel the need to quote this from GNU's philosophy here again. > The word “free” has two legitimate general meanings; it can refer either to freedom or to price. When we speak of “free software”, we're talking about freedom, not price. (Think of “free speech”, not “free beer”.) Specifically, it means that a user is free to run the program, change the program, and redistribute the program with or without changes. --- --- date: "2013-07-19T00:00:00Z" title: MailDrop.cc - Free Throwaway Email Address --- Nice and clean UI. More importantly, it's *not yet* blacklisted by other websites. --- --- date: "2013-07-19T00:00:00Z" link: http://projects.jga.me/layouts/ title: Layouts v2.0 --- An Alfred workflow as a lightweight window manager solution. Heck, it's even more customisable than DoublePane but DoublePanel is more responsive. It takes Layouts a second or two to response to the hotkey. --- --- date: "2013-07-18T00:00:00Z" link: http://h5bp.github.io/Effeckt.css/dist/ title: Effeckt.css --- Lots of cool animation stuff in CSS. --- --- date: "2013-07-14T00:00:00Z" link: http://egocodedinsol.github.io/raised_cosine_basis_functions/ title: A Proposal to A Better Format for Research Paper --- This definitely is not going to work for the time being. The web isn't intended for this kind of stuff because the paper won't look the same cross-browsers. PDF, on another hand, was designed to look exactly the same as the author intends. PDF wins right there. --- --- date: "2013-07-13T00:00:00Z" title: Finally --- Finally, I'm able to grab a generic TLD domain of my name. #winning ---