<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Harianto van Insulinde]]></title><description><![CDATA[Insights on coding. Efficient deployment strategies. A reference for personal growth. Challenging norms—because conventional wisdom isn’t always correct!]]></description><link>https://blog.sylo.space/</link><image><url>https://blog.sylo.space/favicon.png</url><title>Harianto van Insulinde</title><link>https://blog.sylo.space/</link></image><generator>Ghost 5.25</generator><lastBuildDate>Thu, 16 Apr 2026 06:37:59 GMT</lastBuildDate><atom:link href="https://blog.sylo.space/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Custom Docker Compose MCPO]]></title><description><![CDATA[<p>(Guide Work in Progress) Running Ubuntu on my GPD WIN 5</p><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM node:24-alpine3.22

# `apk add go` is not yet good, it installs `go version go1.24.13 linux/arm64` at this time of writing, but need higher version to make Docker MCP Toolkit work with `make` 
# go.mod</code></pre></figure>]]></description><link>https://blog.sylo.space/custom-docker-compose-mcpo/</link><guid isPermaLink="false">69a05bd2f1ef130001d26ec4</guid><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Thu, 26 Feb 2026 14:46:42 GMT</pubDate><content:encoded><![CDATA[<p>(Guide Work in Progress) Running Ubuntu on my GPD WIN 5</p><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM node:24-alpine3.22

# `apk add go` is not yet good, it installs `go version go1.24.13 linux/arm64` at this time of writing, but need higher version to make Docker MCP Toolkit work with `make` 
# go.mod requires go &gt;= 1.25.5
RUN apk add --update --no-cache curl wget python3 docker docker-compose make bash git

# Copy the script to get the latest golang amd64
COPY build/get-latest-golang-amd64.sh /tmp/get-latest-golang-amd64.sh
RUN chmod +x /tmp/get-latest-golang-amd64.sh
RUN /tmp/get-latest-golang-amd64.sh
RUN rm /tmp/get-latest-golang-amd64.sh
ENV PATH=&quot;$PATH:/usr/local/go/bin&quot;
RUN go version

# Copy the script to build the docker mcp toolkit
COPY build/build-docker-mcp-toolkit.sh /tmp/build-docker-mcp-toolkit.sh
RUN chmod +x /tmp/build-docker-mcp-toolkit.sh
RUN /tmp/build-docker-mcp-toolkit.sh
RUN rm /tmp/build-docker-mcp-toolkit.sh

# Install `uv`
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH=&quot;/root/.local/bin:$PATH&quot;

# Copy `mcpo` config
COPY data/config.json /data/config.json

CMD [&quot;uvx&quot;, &quot;mcpo&quot;, &quot;--port&quot;, &quot;8000&quot;, &quot;--config&quot;, &quot;/data/config.json&quot;]
</code></pre><figcaption>File: <code>Dockerfile</code></figcaption></figure><p></p><figure class="kg-card kg-code-card"><pre><code class="language-yml">services:
  mcpo:
    build: .
    restart: unless-stopped
    # ports:
    #   - &quot;8000:8000&quot;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # REQUIRED: For docker mcp inside mcpo
      - /root/.docker/mcp:/root/.docker/mcp:ro  # REQUIRED: Toolkit profiles/servers
</code></pre><figcaption>File: <code>docker-compose.yml</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-yml">networks:
  cloudflared:
    external: true

services:
  mcpo:
    networks:
      - cloudflared
</code></pre><figcaption>(Optional) File: <code>docker-compose.override.yml</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">#!/bin/sh
ORIGIN_URL=&quot;https://go.dev&quot;
LATEST_URL=$(wget -qO- https://go.dev/dl/ | grep -oE &apos;href=&quot;[^&quot;]*linux-amd64\.tar\.gz&quot;&apos; | cut -d&apos;&quot;&apos; -f2 | head -1)
wget -O /tmp/go.tar.gz &quot;$ORIGIN_URL/$LATEST_URL&quot;
rm -rf /usr/local/go
tar -C /usr/local -xzf /tmp/go.tar.gz
rm /tmp/go.tar.gz
export PATH=$PATH:/usr/local/go/bin
echo &apos;export PATH=$PATH:/usr/local/go/bin&apos; &gt;&gt; ~/.profile
go version
</code></pre><figcaption>File: <code>get-latest-golang-amd64.sh</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-shell">#!/bin/sh
# Clone the repository
cd /tmp
git clone --depth 1 https://github.com/docker/mcp-gateway.git
cd mcp-gateway
mkdir -p &quot;$HOME/.docker/cli-plugins/&quot;
# Build and install the plugin
make docker-mcp

docker mcp --help
rm -rf /tmp/mcp-gateway
</code></pre><figcaption>File: <code>build-docker-mcp-toolkit.sh</code></figcaption></figure><p></p>]]></content:encoded></item><item><title><![CDATA[Contemplating to change my blog to something simpler]]></title><description><![CDATA[<p>I am going to try Obsidian and setup with Github together with Cloudflare.</p>]]></description><link>https://blog.sylo.space/contemplating-to-move-my-blog-to-something-simpler/</link><guid isPermaLink="false">67862c4ef8fad400019f2551</guid><category><![CDATA[Journal]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Tue, 14 Jan 2025 09:29:15 GMT</pubDate><content:encoded><![CDATA[<p>I am going to try Obsidian and setup with Github together with Cloudflare.</p>]]></content:encoded></item><item><title><![CDATA[Rsync crash on Mac (M1), use Docker instead]]></title><description><![CDATA[<p>My MacBook crashes after a while copying files using rsync. It&#x2019;s been 5 more times already and I was about to give up. Until I got an idea using docker.</p><p>So I tried:</p><pre><code class="language-bash">rsync -vaP ~/source /Volume/destination/HD</code></pre><p>Straightforward stuff, but my Mac crashes.</p><p>So I use</p>]]></description><link>https://blog.sylo.space/rsync-crash-on-mac-m1-use-docker-instead/</link><guid isPermaLink="false">673e30bbac73f6000139d344</guid><category><![CDATA[Journal]]></category><category><![CDATA[Macinthosh]]></category><category><![CDATA[Alpine]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Wed, 20 Nov 2024 19:03:31 GMT</pubDate><content:encoded><![CDATA[<p>My MacBook crashes after a while copying files using rsync. It&#x2019;s been 5 more times already and I was about to give up. Until I got an idea using docker.</p><p>So I tried:</p><pre><code class="language-bash">rsync -vaP ~/source /Volume/destination/HD</code></pre><p>Straightforward stuff, but my Mac crashes.</p><p>So I use docker instead.</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">docker run --rm -v ~/source:/source -v /Volume/destination/HD:/destination -it alpine sh</code></pre><figcaption>Terminal</figcaption></figure><p>Then in Alpine installing <code>rsync</code> is a breeze.</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">apk add rsync

rsync -vaP /source /destination</code></pre><figcaption>Docker Alpine Shell</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Install Alpine Linux on VPS TransIP]]></title><description><![CDATA[<p>The road of installing Alpine v3.20.3 Virtual x86_64.</p><!--kg-card-begin: html--><script type="module">
  import { createApp } from 'petite-vue'

  createApp({
    hostName: '136-144-129-179',
    domainName: 'colo.transip.net'
  }).mount()
</script>

<style>
    .input-field {
        background-color: transparent;
    }
</style><!--kg-card-end: html--><!--kg-card-begin: html--><span v-scope><!--kg-card-end: html--><h1 id="prerequisite">Prerequisite</h1><!--kg-card-begin: html--><ol>
    <li>VPS: minimap 1 GB Memory</li>
    <li>Access to GRUB, starting with Debian default installation.<br>
        Remember - <em>Hostname:</em> <code><input class="input-field" v-model="hostName"></code><br>
        Remember - <em>Domain name:</em> <code><input class="input-field" v-model="domainName"></code><br></li>
</ol><!--kg-card-end: html--><h1 id="debian-installation">Debian installation</h1><blockquote>During</blockquote></span>]]></description><link>https://blog.sylo.space/install-alpine-linux-on-vps-transip/</link><guid isPermaLink="false">66fe7264fdb218000123ac3e</guid><category><![CDATA[Journal]]></category><category><![CDATA[VPS]]></category><category><![CDATA[TransIP]]></category><category><![CDATA[Alpine]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Mon, 04 Nov 2024 14:03:25 GMT</pubDate><content:encoded><![CDATA[<p>The road of installing Alpine v3.20.3 Virtual x86_64.</p><!--kg-card-begin: html--><script type="module">
  import { createApp } from 'petite-vue'

  createApp({
    hostName: '136-144-129-179',
    domainName: 'colo.transip.net'
  }).mount()
</script>

<style>
    .input-field {
        background-color: transparent;
    }
</style><!--kg-card-end: html--><!--kg-card-begin: html--><span v-scope><!--kg-card-end: html--><h1 id="prerequisite">Prerequisite</h1><!--kg-card-begin: html--><ol>
    <li>VPS: minimap 1 GB Memory</li>
    <li>Access to GRUB, starting with Debian default installation.<br>
        Remember - <em>Hostname:</em> <code><input class="input-field" v-model="hostName"></code><br>
        Remember - <em>Domain name:</em> <code><input class="input-field" v-model="domainName"></code><br></li>
</ol><!--kg-card-end: html--><h1 id="debian-installation">Debian installation</h1><blockquote>During the installation remember <em><strong>Hostname</strong></em> and <strong><em>Domain name</em></strong>.</blockquote><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">Summary Debian Configuration</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><ul><li>Hostname: <code>{{hostName}}</code></li><li>Domain name: <code>colo.transip.net</code></li><li>Language: <code>English - English</code></li><li>Country, territory or area: <code>Other / Europe / Netherlands</code></li><li>Country to base default locale settings on: <code>United Kindom - en_GB.UTF-8</code></li><li>Keymap to use: <code>American English</code></li><li>HTTP Proxy information: blank</li><li>Root password: <code>********</code></li><li>Re-enter password to verify: <code>********</code></li><li>Full name for the new user: <code>Harianto</code></li><li>Username for your account: <code>harianto</code></li><li>Choose a password for the new user: <code>********</code></li><li>Partitioning method: <code>Guided - use entire disk</code></li><li>Select disk to partition: <code>Virtual disk 1 (vda) - 161.1 GB Virtio Block Device</code></li><li>Partitioning Scheme: <code>All files in one partition (recommended for new users)</code></li><li><code>Finish partitioning and write change to disk</code></li><li>Write the changes to disk?: <code>Yes</code></li><li>Participate in the package usage survey?: <code>FUCK NO</code></li><li>Choose only software to install: <code>SSH server</code> and <code>standard system utilities</code></li><li>Install the GRUB boot loader to your primary drive? <code>Yes</code></li><li>Device for boot loader installation: <code>/dev/vda (virtio-969d82dd5e3a3a7c4cdf</code></li></ul></div></div><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-12.50.38.png" class="kg-image" alt loading="lazy" width="1392" height="912" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/Screenshot-2024-10-03-at-12.50.38.png 600w, https://blog.sylo.space/content/images/size/w1000/2024/10/Screenshot-2024-10-03-at-12.50.38.png 1000w, https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-12.50.38.png 1392w" sizes="(min-width: 720px) 720px"><figcaption>Partition disks</figcaption></figure><blockquote>Virtual disk 1 (vda) - 161.1 GB Virtio Block Device<br>#1 primary &#xA0; 160 GB. f ext4 /<br>#5 logical 1.0 GB f swap swap</blockquote><h1 id="prepare-before-we-go-into-grub-shell">Prepare before we go into GRUB shell</h1><ul><li><a href="https://alpinelinux.org/downloads/">Download</a> the Alpine Virtua ISO x86_64 file</li><li>Move ISO to <code>/</code> directory (for easy access)</li><li>(optional) Check ISO <code>/boot</code> for: <br>- <code>vmlinuz-virt</code> <br>- <code>initramfs-virt</code></li></ul><figure class="kg-card kg-code-card"><pre><code class="language-sh"># Go to root
cd /
# Download latest Alpine Virtual
wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-virt-3.20.3-x86_64.iso
</code></pre><figcaption>Shell</figcaption></figure><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">(Optional) Check ISO boot files</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p><code>sudo mount-o loop /alpine-virt-3.20.3-x86_64.iso /mnt/</code></p><p><code>ls-l /mnt/boot/</code></p><p>&gt; see if those files <code>vmlinuz-virt</code> and <code>initramfs-virt</code> exists</p><p><code>sudo umount /mnt</code></p></div></div><figure class="kg-card kg-image-card"><img src="https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-13.25.44.png" class="kg-image" alt loading="lazy" width="1405" height="1204" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/Screenshot-2024-10-03-at-13.25.44.png 600w, https://blog.sylo.space/content/images/size/w1000/2024/10/Screenshot-2024-10-03-at-13.25.44.png 1000w, https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-13.25.44.png 1405w" sizes="(min-width: 720px) 720px"></figure><h2 id="reboot">Reboot</h2><p>After reboot make sure you can press <code>c</code>.</p><pre><code class="language-sh">reboot</code></pre><h1 id="booting-grub-into-shell">Booting GRUB into Shell</h1><figure class="kg-card kg-image-card"><img src="https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-13.34.09.png" class="kg-image" alt loading="lazy" width="979" height="827" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/Screenshot-2024-10-03-at-13.34.09.png 600w, https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-03-at-13.34.09.png 979w" sizes="(min-width: 720px) 720px"></figure><p>Use arrow keys to stop countdown, then press <code>c</code> button.</p><figure class="kg-card kg-image-card"><img src="https://blog.sylo.space/content/images/2024/10/grub-cl.png" class="kg-image" alt loading="lazy" width="800" height="149" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/grub-cl.png 600w, https://blog.sylo.space/content/images/2024/10/grub-cl.png 800w" sizes="(min-width: 720px) 720px"></figure><h2 id="grub-terminal">GRUB terminal</h2><p>Boot Alpine from ISO</p><figure class="kg-card kg-code-card"><pre><code class="language-shell"># loopback
loopback l /alpine-virt-3.20.3-x86_64.iso
# attach boot
linux (l)/boot/vmlinuz-virt
initrd (l)/boot/initramfs-virt
# exec
boot</code></pre><figcaption>Commands to boot from Alpine ISO</figcaption></figure><blockquote>A few seconds after you enter the boot command, the init system stops with an error message such as <code>Mounting boot media: failed</code>. <br>Then continue in the Emergency Recovery Shell.</blockquote><h2 id="alpine-emergency-recovery-shell">Alpine Emergency Recovery Shell</h2><p>Coping the ISO to <code>/dev/shm</code> .</p><p><strong><code>/dev/shm</code></strong> is a special file system in Unix-like operating systems, specifically designed for shared memory. It provides a temporary, high-speed storage area that is accessible to all processes on the system.</p><pre><code class="language-shell">mount /dev/vda1 /media/vda1
cp /media/vda1/alpine-virt-3.20.3-x86_64.iso /dev/shm
umount /dev/vda1
mount -o loop -t iso9660 /dev/shm/alpine-virt-3.20.3-x86_64.iso /media/cdrom
</code></pre><blockquote>CTRL-D or type: <code>exit</code></blockquote><p>This will boot finally the Alpine Installation Mode</p><h1 id="installing-alpine-v3203">Installing Alpine v3.20.3</h1><p>Let&#x2019;s start with: <code>setup-alpine</code></p><p>Your setup will look like this: <a href="#debian-installation">Debian Installation</a></p><p>Hostname: <code>{{hostName}}</code><br>Domain name: <code>{{domainName}}</code></p><blockquote>When it ask for a repository mirror: use automatically find best mirrors. <br>It takes long, but I had sometimes no connection if I choose &#xA0;<code>1</code>.</blockquote><p>Just follow the instructions, pick the correct device, such as <code>/dev/vda</code></p><p>Time to reboot!</p><h2 id="optional-after-the-reboot">(Optional) After the reboot</h2><ul><li>Configure SSH</li><li>Create Snapshot in your TransIP Control Panel</li><li>Enable <code>community</code> in your <code>/etc/apk/respositories</code></li><li>Install <code>docker</code> and <code>docker-compose</code></li><li>Attach Big Storage and use that as your Docker installs</li><li>Replace <code>/var/lib/docker</code> with the directory from Big Storage</li></ul><h1 id="rest">Rest</h1><p>Congratulation for installing Alpine in your VPS</p><!--kg-card-begin: html--></span>
<!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Making ACME-DNS self-host work]]></title><description><![CDATA[So you‘ve got a VPS and want to self-host ACME-DNS for your new domain CA certificates]]></description><link>https://blog.sylo.space/making-acme-dns-self-host-work/</link><guid isPermaLink="false">67138a6ffdb218000123aee0</guid><category><![CDATA[Alpine]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Sat, 19 Oct 2024 13:34:30 GMT</pubDate><content:encoded><![CDATA[<p>Your new domain: <code>newdomain.com</code><br>Your ACME DNS self-host domain: <code>myacmedns.com</code></p><blockquote>Example domain names.</blockquote><h1 id="client">Client</h1><p>So you&#x2018;ve got a domain <code>newdomain.com</code> and you want to have certificates with wildcard such as <code>*.newdomain.com</code>.</p><p>Let&#x2019;s build our own client:</p><pre><code># directory structure
/docker/acmedns-client/
  build/
    Dockerfile
  docker-build.sh
  docker-run.sh
  acmedns-client.sh
  certbot.sh</code></pre><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM alpine:latest as BUILDER

RUN apk add --no-cache go git

WORKDIR /data

RUN git clone --depth 1 https://github.com/acme-dns/acme-dns-client &amp;&amp; \
    cd acme-dns-client &amp;&amp; \
    go get &amp;&amp; \
    go build

FROM alpine:latest
RUN apk add --no-cache certbot

# Copy the built binary from the BUILDER stage
COPY --from=BUILDER /data/acme-dns-client/acme-dns-client /usr/local/bin/acme-dns-client

CMD [&quot;/usr/local/bin/acme-dns-client&quot;]</code></pre><figcaption>alpine:<strong>3.20.3</strong>, file: <code>build/Dockerfile</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-sh">#!/bin/bash
docker build -t harianto/acmedns-client -f build/Dockerfile build</code></pre><figcaption>file: docker-build.sh, docker image: <code>harianto/acmedns-client</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-sh">#!/bin/bash
mkdir -p data/letsencrypt
docker run --rm -v ${pwd}/data/letsencrypt:/etc/letsencrypt -it harianto/acmedns-client sh</code></pre><figcaption>File: docker-run.sh</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-sh">#!/bin/bash
mkdir -p data/letsencrypt
docker run --rm \
-v ${pwd}/data/letsencrypt:/etc/letsencrypt \
-it harianto/acmedns-client certbot \
  certonly \
  --manual \
  --preferred-challenges dns \
  --server https://acme-staging-v02.api.letsencrypt.org/directory \
  --agree-tos \
  -m letsencrypt@newdomain.com \
  --no-eff-email \
  --manual-auth-hook &apos;/usr/local/bin/acme-dns-client&apos; \
  &quot;$@&quot;</code></pre><figcaption>File: certbot.sh</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-sh">#!/bin/bash
docker run --rm \
-v ${pwd}/data/letsencrypt:/etc/letsencrypt \
-it harianto/acmedns-client acme-dns-client &quot;$@&quot;
</code></pre><figcaption>File: acmedns-client.sh</figcaption></figure><blockquote>Don&#x2019;t for get to <code>chmod +x</code> your <code>*.sh</code> files</blockquote><h2 id="following-scripts-to-run">Following scripts to run</h2><ul><li>Build once: <code>./docker-build.sh</code>. Your docker image would be <code>harianto/acmedns-client</code></li></ul><blockquote>From this point you can run Docker Image <code>harianto/acmedns-client</code> however you want.</blockquote><ul><li>Register once: <code>./acmedns-client.sh register -s <a href="https://auth.acme-dns.io">https://auth.acme-dns.io</a> --dangerous -d newdomain.com</code></li></ul><blockquote>We&#x2019;ll be using <code>https://auth.acme-dns.io</code> for test, before we create our server, for example: <code>https://auth.myacmedns.com</code></blockquote><blockquote>You can ignore CAA record, for now.</blockquote><p>It will prompt like this:</p><pre><code class="language-shell">[*] New acme-dns account for domain newdomain.com successfully registered!

Do you want acme-dns-client to monitor the CNAME record change? [Y/n]: n
Domain:         7f1449b3-9371-4b6a-a472-6ab79764dae7.auth.acme-dns.io

To finalize the setup, you need to create a CNAME record pointing from _acme-challenge.newdomain.com
to the newly created acme-dns domain 7f1449b3-9371-4b6a-a472-6ab79764dae7.auth.acme-dns.io

A correctly set up CNAME record should look like the following:

_acme-challenge.newdomain.com.     IN      CNAME   7f1449b3-9371-4b6a-a472-6ab79764dae7.auth.acme-dns.io.</code></pre><p>You have to go to your DNS Settings in the Control Panel for your <code>newdomain.com</code>. And add a record:</p><pre><code>_acme-challenge.newdomain.com.     IN      CNAME   7f1449b3-9371-4b6a-a472-6ab79764dae7.auth.acme-dns.io.</code></pre><p>That might look like this.</p><figure class="kg-card kg-image-card"><img src="https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-19-at-15.47.15.png" class="kg-image" alt loading="lazy" width="1130" height="288" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/Screenshot-2024-10-19-at-15.47.15.png 600w, https://blog.sylo.space/content/images/size/w1000/2024/10/Screenshot-2024-10-19-at-15.47.15.png 1000w, https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-19-at-15.47.15.png 1130w" sizes="(min-width: 720px) 720px"></figure><ul><li>Then run certbot (every 3 months) and see magic happens: </li></ul><figure class="kg-card kg-code-card"><pre><code class="language-bash">./certbot.sh -d newdomain.com -d *.newdomain.com</code></pre><figcaption>Your certificates will be stored in <code>./data/letsencrypt/live/newdomain.com</code></figcaption></figure><blockquote>Your certificates will be saved in <code>./data/letsencrypt/live/newdomain.com</code> directory.</blockquote><p>From this point, your should not having errors. Then you can create your own self-host ACME DNS. And have to register again with new (but once) and change your CNAME record (once again).</p><h1 id="server">Server</h1><p>So You want to self-host your ACME-DNS on <code>myacmedns.com</code>.</p><pre><code># directory structure
/docker/acmedns</code></pre><h2 id="build">Build</h2><p>We can use the Docker Image <code>joohoi/acme-dns</code> or we build from his git repository.</p><blockquote>There were few bumps installing, but one of the solution is to change Dockerfile and add new environment variable before <code>go build</code>.</blockquote><p>Let&#x2019;s create a directory: <code>/docker/acmedns</code></p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># create directory
mkdir -p /docker/acmedns
# goto
cd /docker/acmedns
# git clone: depth 1
git clone --depth 1 https://github.com/acme-dns/acme-dns.git build
# create empty file and chmod +x
touch docker-build.sh; chmod +x docker-build.sh</code></pre><figcaption>Shell</figcaption></figure><blockquote>At this point <code>build</code> folder is created, and then we create <code>docker-build.sh</code> file.</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-sh">#!/bin/bash
docker build -t harianto/acmedns-server -f build/Dockerfile build</code></pre><figcaption>File: /docker/acmedns/docker-build.sh, docker image: harianto/acmedns-server</figcaption></figure><blockquote>running <code>./docker-build.sh</code> now, you find some errors.</blockquote><p>Edit: <code>/docker/acmedns/build/Dockerfile</code></p><p>Edit a line and inlude this <code>CGO_CFLAGS=&quot;-D_LARGEFILE64_SOURCE&quot;</code> </p><pre><code># code here ...
RUN CGO_ENABLED=1 CGO_CFLAGS=&quot;-D_LARGEFILE64_SOURCE&quot; go build

# code here ...</code></pre><p>Dockerfile should look similar like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM golang:alpine AS builder
LABEL maintainer=&quot;joona@kuori.org&quot;

RUN apk add --update gcc musl-dev git

ENV GOPATH /tmp/buildcache
RUN git clone https://github.com/joohoi/acme-dns /tmp/acme-dns
WORKDIR /tmp/acme-dns
RUN CGO_ENABLED=1 CGO_CFLAGS=&quot;-D_LARGEFILE64_SOURCE&quot; go build

FROM alpine:latest

WORKDIR /root/
COPY --from=builder /tmp/acme-dns .
RUN mkdir -p /etc/acme-dns
RUN mkdir -p /var/lib/acme-dns
RUN rm -rf ./config.cfg
RUN apk --no-cache add ca-certificates &amp;&amp; update-ca-certificates

VOLUME [&quot;/etc/acme-dns&quot;, &quot;/var/lib/acme-dns&quot;]
ENTRYPOINT [&quot;./acme-dns&quot;]
EXPOSE 53 80 443
EXPOSE 53/udp</code></pre><figcaption>line changed: <code>RUN CGO_ENABLED=1 CGO_CFLAGS=&quot;-D_LARGEFILE64_SOURCE&quot; go build</code></figcaption></figure><p>Now we can build.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># goto dir
cd /docker/acmedns
# build it
./docker-build.sh</code></pre><figcaption>Docker Image <code>harianto/acmedns-server</code> is created</figcaption></figure><h2 id="create-docker-composeyml">Create docker-compose.yml</h2><figure class="kg-card kg-code-card"><pre><code class="language-yml"># create volume: docker volume create --name=letsencrypt
#volumes:
#  letsencrypt:
#    external: true

services:
  acmedns:
    image: harianto/acmedns-server
    container_name: acmedns
    #network_mode: host
    ports:
      - &quot;53:53&quot;
      - &quot;53:53/udp&quot;
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    volumes:
      - ./data/acmedns_config:/etc/acme-dns:ro
      - ./data/acmedns_acmd-dns:/var/lib/acme-dns
      #- letsencrypt:/etc/letsencrypt:ro
    restart: unless-stopped  # Optional: to ensure the container restarts on failure</code></pre><figcaption>File: /docker/acmedns/docker-compose.yml</figcaption></figure><blockquote>Parts that are commented <code>#</code> would be handy for advanced things, uncomment them any time.</blockquote><blockquote>Running with: <code>docker-compose up</code> you might see an error that no <code>config.cfg</code> is available.</blockquote><p>You can copy <code>config.cfg</code> from the <code>build</code> folder and paste it to <code>./data/acmedns_config</code></p><figure class="kg-card kg-code-card"><pre><code class="language-ini">[general]
# DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53
# In this case acme-dns will error out and you will need to define the listening interface
# for example: listen = &quot;127.0.0.1:53&quot;
listen = &quot;0.0.0.0:53&quot;
# protocol, &quot;both&quot;, &quot;both4&quot;, &quot;both6&quot;, &quot;udp&quot;, &quot;udp4&quot;, &quot;udp6&quot; or &quot;tcp&quot;, &quot;tcp4&quot;, &quot;tcp6&quot;
protocol = &quot;both&quot;
# domain name to serve the requests off of
domain = &quot;auth.myacmedns.com&quot;
# zone name server
nsname = &quot;auth.myacmedns.com&quot;
# admin email address, where @ is substituted with .
nsadmin = &quot;admin.myacmedns.com&quot;
# predefined records served in addition to the TXT
records = [
    # domain pointing to the public IP of your acme-dns server 
    &quot;auth.myacmedns.com. A 31.14.98.159&quot;,
    # specify that auth.myacmedns.com will resolve any *.auth.myacmedns.com records
    &quot;auth.myacmedns.com. NS auth.myacmedns.com.&quot;,
]
# debug messages from CORS etc
debug = false

[database]
# Database engine to use, sqlite3 or postgres
engine = &quot;sqlite3&quot;
# Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres
# Please note that the default Docker image uses path /var/lib/acme-dns/acme-dns.db for sqlite3
connection = &quot;/var/lib/acme-dns/acme-dns.db&quot;
# connection = &quot;postgres://user:password@localhost/acmedns_db&quot;

[api]
# listen ip eg. 127.0.0.1
ip = &quot;0.0.0.0&quot;
# disable registration endpoint
disable_registration = false
# listen port, eg. 443 for default HTTPS
port = &quot;443&quot;
# possible values: &quot;letsencrypt&quot;, &quot;letsencryptstaging&quot;, &quot;cert&quot;, &quot;none&quot;
tls = &quot;cert&quot;
# only used if tls = &quot;cert&quot;
tls_cert_privkey = &quot;/etc/letsencrypt/live/myacmedns.com/privkey.pem&quot;
tls_cert_fullchain = &quot;/etc/letsencrypt/live/myacmedns.com/fullchain.pem&quot;
# only used if tls = &quot;letsencrypt&quot;
acme_cache_dir = &quot;api-certs&quot;
# optional e-mail address to which Let&apos;s Encrypt will send expiration notices for the API&apos;s cert
notification_email = &quot;&quot;
# CORS AllowOrigins, wildcards can be used
corsorigins = [
    &quot;*&quot;
]
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = &quot;X-Forwarded-For&quot;

[logconfig]
# logging level: &quot;error&quot;, &quot;warning&quot;, &quot;info&quot; or &quot;debug&quot;
loglevel = &quot;debug&quot;
# possible values: stdout, TODO file &amp; integrations
logtype = &quot;stdout&quot;
# file path for logfile TODO
# logfile = &quot;./acme-dns.log&quot;
# format, either &quot;json&quot; or &quot;text&quot;
logformat = &quot;text&quot;
</code></pre><figcaption>File: /docker/acmedns/data/acmedns_config/config.cfg</figcaption></figure><blockquote>Change to <code>port=&quot;80</code>&quot;, and <code>tls=&quot;none</code>&quot; if your server don&#x2019;t have certificates yet. And register server running this:<br><code>./acmedns-client.sh register -s <a href="https://auth.acme-dns.io/">http://auth.myacmedns.com</a></code>.</blockquote><h2 id="change-dns-settings">Change DNS Settings</h2><p>For my newly discovery that works. Your DNS Settings need to able to add <code>NS</code> records. I&#x2019;ve tried with <code>A</code> records, but it doesn&#x2019;t work.</p><blockquote>There can only be one record with <code>auth</code>.</blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-19-at-12.39.57.png" class="kg-image" alt loading="lazy" width="2000" height="501" srcset="https://blog.sylo.space/content/images/size/w600/2024/10/Screenshot-2024-10-19-at-12.39.57.png 600w, https://blog.sylo.space/content/images/size/w1000/2024/10/Screenshot-2024-10-19-at-12.39.57.png 1000w, https://blog.sylo.space/content/images/size/w1600/2024/10/Screenshot-2024-10-19-at-12.39.57.png 1600w, https://blog.sylo.space/content/images/2024/10/Screenshot-2024-10-19-at-12.39.57.png 2362w" sizes="(min-width: 720px) 720px"><figcaption>DNS record from a self-host where your own ACME DNS would be in.</figcaption></figure><blockquote><code>@</code> points to your public address <code>A</code> <code>11.22.33.44</code> ,<br><code>auth</code>.myacmedns.com points to <code>NS</code> <code>myacmedns.com.</code></blockquote><h2 id="let%E2%80%99s-go-back-to-our-client">Let&#x2019;s go back to our Client</h2><p>Going back to your <code>newdomain.com</code> terminal, we need to run some commands again, to <em>register</em> and creating <em>certificates</em>.</p><h3 id="register-once">Register once</h3><p>With your self-host server, you can update new server path.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># goto
cd /docker/acmedns-client
# register once
./acmedns-client.sh register -s https://auth.myacmedns.com
</code></pre><figcaption>Is your Acme DNS config.cfg port to 80, then use: http://auth.myacmedns.com</figcaption></figure><p>Follow instructions and remember <code>CNAME</code> record, for example:</p><pre><code>_acme-challenge.newdomain.com.     IN      CNAME   7f1449b3-9371-4b6a-a472-6ab79764dae7.auth.myacmedns.com.</code></pre><h3 id="update-dns-settings-once">Update DNS Settings once</h3><p>Go to your control panel and update your DNS records.</p><h3 id="get-your-certificates-and-see-the-magic-happens">Get your certificates and see the magic happens</h3><pre><code class="language-bash"># goto
cd /docker/acmedns-client
# get certificates
./certbot.sh -d newdomain.com -d *.newdomain.com</code></pre><h1 id="does-it-work-for-you">Does it work for you?</h1><p>Let me know if this works for you, or give me a thumbs up!</p>]]></content:encoded></item><item><title><![CDATA[Never buy items without PayPal or Klarna]]></title><description><![CDATA[<p>When websites made so good, thats good to be true. But it is not.</p><p>Bought item from Amsterdam, but they deliver from China &#xA0;</p>]]></description><link>https://blog.sylo.space/never-buy-items-without-paypal-or-klarna/</link><guid isPermaLink="false">670d941bfdb218000123aec6</guid><category><![CDATA[Journal]]></category><category><![CDATA[SCAM]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Mon, 14 Oct 2024 22:02:12 GMT</pubDate><content:encoded><![CDATA[<p>When websites made so good, thats good to be true. But it is not.</p><p>Bought item from Amsterdam, but they deliver from China &#xA0;</p>]]></content:encoded></item><item><title><![CDATA[My Sieve script]]></title><description><![CDATA[<p>Anti spam script</p><p></p><pre><code>require [&quot;fileinto&quot;, &quot;body&quot;, &quot;imap4flags&quot;, &quot;regex&quot;, &quot;envelope&quot;, &quot;spamtest&quot;, &quot;relational&quot;, &quot;comparator-i;ascii-numeric&quot;, &quot;fileinto&quot;, &quot;imap4flags&quot;];

# Check for spam score
if allof (spamtest :value &quot;ge&quot; :comparator &quot;i;ascii-numeric&</code></pre>]]></description><link>https://blog.sylo.space/my-sieve-script/</link><guid isPermaLink="false">670c3aeffdb218000123aeb3</guid><category><![CDATA[Journal]]></category><category><![CDATA[Email]]></category><category><![CDATA[Sieve]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Sun, 13 Oct 2024 21:27:40 GMT</pubDate><content:encoded><![CDATA[<p>Anti spam script</p><p></p><pre><code>require [&quot;fileinto&quot;, &quot;body&quot;, &quot;imap4flags&quot;, &quot;regex&quot;, &quot;envelope&quot;, &quot;spamtest&quot;, &quot;relational&quot;, &quot;comparator-i;ascii-numeric&quot;, &quot;fileinto&quot;, &quot;imap4flags&quot;];

# Check for spam score
if allof (spamtest :value &quot;ge&quot; :comparator &quot;i;ascii-numeric&quot; &quot;5&quot;) {
    fileinto &quot;Spam&quot;;
    stop;
}

# Check for suspicious subject lines
if header :regex &quot;Subject&quot; [&quot;(?i)\\b(viagra|cialis|enlargement|\\$\\$\\$)\\b&quot;] {
    fileinto &quot;Spam&quot;;
    stop;
}

# Check for missing or suspicious From headers
if anyof (not exists &quot;From&quot;,
          header :regex &quot;From&quot; &quot;(?i)\\b(mailer-daemon|postmaster)@&quot;) {
    fileinto &quot;Spam&quot;;
    stop;
}

# Check for suspicious content in the body
if body :raw :contains [&quot;click here&quot;, &quot;limited time offer&quot;, &quot;act now&quot;, &quot;congratulations&quot;, &quot;you&apos;ve won&quot;] {
    fileinto &quot;Spam&quot;;
    stop;
}

# Check for Authentication-Results header
if header :contains &quot;Authentication-Results&quot; [&quot;dkim=fail&quot;, &quot;spf=fail&quot;, &quot;dmarc=fail&quot;] {
    fileinto &quot;Spam&quot;;
    stop;
}

# If none of the above conditions are met, keep the message in the inbox
keep;
</code></pre>]]></content:encoded></item><item><title><![CDATA[Poste.io Mailserver]]></title><description><![CDATA[<p>My poste.io configuration together with my xnmp-vhosts docker and letsencrypt certificates</p><p></p><h1 id="xnmp-vhost">XNMP-VHOST</h1><p>And part configuration from my other Docker Composer.</p><figure class="kg-card kg-code-card"><pre><code class="language-yml"># create volume: docker volume create --name=letsencrypt
volumes:
  letsencrypt:
    external: true

services:
  nginx:
    image: nginx:alpine
    restart: always
    links:
      - 8-0-0-fpm-ext
    ports:
      - &quot;80:80&quot;
      - &quot;</code></pre></figure>]]></description><link>https://blog.sylo.space/poste-io-mailserver/</link><guid isPermaLink="false">6702e80bfdb218000123ae70</guid><category><![CDATA[Draft]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Sun, 06 Oct 2024 19:49:52 GMT</pubDate><content:encoded><![CDATA[<p>My poste.io configuration together with my xnmp-vhosts docker and letsencrypt certificates</p><p></p><h1 id="xnmp-vhost">XNMP-VHOST</h1><p>And part configuration from my other Docker Composer.</p><figure class="kg-card kg-code-card"><pre><code class="language-yml"># create volume: docker volume create --name=letsencrypt
volumes:
  letsencrypt:
    external: true

services:
  nginx:
    image: nginx:alpine
    restart: always
    links:
      - 8-0-0-fpm-ext
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    volumes:
      - ./data/nginx/enabled:/etc/nginx/conf.d
      - ./data/nginx/snippets:/nginx/snippets
      - ./data/nginx/certificates:/nginx/certificates
      - letsencrypt:/etc/letsencrypt:ro
    volumes_from:
      - data

  8-0-0-fpm-ext:
    build: build/8-0-0-fpm-ext
    restart: always
    volumes_from:
      - data

  db:
    image: mariadb:10.5.8 #v10.5.8
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - ./data/db:/var/lib/mysql

  dbadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    environment:
      PMA_HOST: db
      PMA_USER: root # Remove line for production
      PMA_PASSWORD: root # Remove line for production
    depends_on:
      - db

  data:
    image: alpine:latest
    command: echo &quot;--- Docker data volume READY.&quot;
    volumes:
      - ./data/vhosts:/vhosts
      - ./data/tmp:/tmp

# create network: docker network create xnmp-network
networks:
  default:
    name: xnmp-network
    external: true</code></pre><figcaption>harianto XNMP-VHOST: docker-compose.yml</figcaption></figure><blockquote>Follow my guide here: <a href="https://blog.sylo.space/guide-to-install-nginx-php-mariadb-phpmyadmin-in-docker/">XNMP-VHOSTS</a><br>My custom Docker build with Letsencrypt inside</blockquote><p></p><h1 id="my-posteio">My Poste.io</h1><figure class="kg-card kg-code-card"><pre><code class="language-yml"># create volume: docker volume create --name=letsencrypt
volumes:
  letsencrypt:
    external: true

services:
  mailserver:
    image: analogic/poste.io:latest
    hostname: mail.harianto.dev
    environment:
      - TZ=Europe/Amsterdam
      - MODE=pro
    volumes:
      - ./data/mailserver:/data
      - letsencrypt:/etc/letsencrypt
    ports:
      # - 80:80/tcp   # HTTP
      # - 443:443/tcp # HTTPS
      - 25:25/tcp   # SMTP
      - 465:465/tcp # SMTPS
      - 587:587/tcp # SMTP
      - 110:110/tcp # POP3
      - 995:995/tcp # POP3S
      - 143:143/tcp # IMAP
      - 993:993/tcp # IMAPS
      - 4190:4190/tcp # Sieve
    # network_mode: host

# create network: docker network create xnmp-network
networks:
  default:
    name: xnmp-network
    external: true</code></pre><figcaption>Poste.io Mailserver: docker-compose.yml</figcaption></figure><pre><code class="language-bash">docker-compose exec mailserver bash</code></pre><p>inside mailserver terminal</p><figure class="kg-card kg-code-card"><pre><code class="language-bash"># add user `mail` to `root` group
sudo adduser mail root

# make a symlink from a mounted letsencrypt
cd /data/ssl
rm -rf letsencrypt
ln -nsf /etc/letsencrypt/live /data/ssl/letsencrypt

# exit docker terminal
exit</code></pre><figcaption>Mailserver Terminal</figcaption></figure><blockquote>This make sure <code>mail</code> user have read/write access to <code>root</code> files, so it can read letsencrypt files</blockquote><p>Restart Docker</p><pre><code class="language-bash">docker-compose restart mailserver</code></pre>]]></content:encoded></item><item><title><![CDATA[Created my own Nuxt 3 template]]></title><description><![CDATA[<p>I&#x2019;ve just created my own starter Nuxt 3 boilerplate. It save me some time for creating new project and tools.</p><p>Just run:</p><pre><code class="language-bash">npx nuxi init my-nuxt-app -t gh:HariantoAtWork/nuxt-3-boilerplate</code></pre>]]></description><link>https://blog.sylo.space/created-my-own-nuxt-3-template/</link><guid isPermaLink="false">66ceff1daeb53d0001dc1b45</guid><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Wed, 28 Aug 2024 10:44:20 GMT</pubDate><content:encoded><![CDATA[<p>I&#x2019;ve just created my own starter Nuxt 3 boilerplate. It save me some time for creating new project and tools.</p><p>Just run:</p><pre><code class="language-bash">npx nuxi init my-nuxt-app -t gh:HariantoAtWork/nuxt-3-boilerplate</code></pre>]]></content:encoded></item><item><title><![CDATA[@harianto/axioscancelable]]></title><description><![CDATA[<p>I recently developed a new <a href="https://www.npmjs.com/package/@harianto/axioscancelable">NPM</a> module, an enhanced version of axiosBluebird with ESM support. This module simplifies asynchronous operations and provides better control over requests. Create instances with AbortController included and experience the benefits. Download version v1.4.0 today!</p><h1 id="install">Install</h1><figure class="kg-card kg-code-card"><pre><code class="language-bash">npm i @harianto/axioscancelable</code></pre><figcaption>Terminal</figcaption></figure><!--kg-card-begin: markdown--><h1 id="axioscancelable">AxiosCancelable</h1>
<p>Axios with</p>]]></description><link>https://blog.sylo.space/harianto-axioscancelable/</link><guid isPermaLink="false">66cc60daaeb53d0001dc1afd</guid><category><![CDATA[Journal]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Mon, 26 Aug 2024 11:12:09 GMT</pubDate><content:encoded><![CDATA[<p>I recently developed a new <a href="https://www.npmjs.com/package/@harianto/axioscancelable">NPM</a> module, an enhanced version of axiosBluebird with ESM support. This module simplifies asynchronous operations and provides better control over requests. Create instances with AbortController included and experience the benefits. Download version v1.4.0 today!</p><h1 id="install">Install</h1><figure class="kg-card kg-code-card"><pre><code class="language-bash">npm i @harianto/axioscancelable</code></pre><figcaption>Terminal</figcaption></figure><!--kg-card-begin: markdown--><h1 id="axioscancelable">AxiosCancelable</h1>
<p>Axios with custom CancelablePromise cancelation</p>
<blockquote>
<p><em>Node</em>: v20.16.0<br>
<em>NPM</em>: 10.8.1</p>
</blockquote>
<blockquote>
<p>This is ESM variant from NPM: axiosbluebird.</p>
</blockquote>
<h2 id="how-to-usefactoryaxioscancelable">How to use - factoryAxiosCancelable</h2>
<pre><code class="language-js">import { factoryAxiosCancelable, isCancel } from &apos;@harianto/axioscancelable&apos;
</code></pre>
<h3 id="method-get">method: GET</h3>
<pre><code class="language-js">const getDefaultConfig = {
  method: &apos;get&apos;,
  url: &apos;https://api.sylo.space/test/axioscancelable/data&apos;
}

// Factory (Instantiate)
const getData = factoryAxiosCancelable(getDefaultConfig)

// See Axios Config: params
const firstRequest = getData({
  params: {
    id: 12345
  }
})

firstRequest
  .then(data =&gt; {
    console.log(&apos;SUCCESS firstRequest!!!&apos;, data)
  })
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted firstRequest&apos;)
    } else {
      console.error(error)
    }
  })

const secondRequest = getData({
  params: {
    id: 67890
  }
})

secondRequest
  .then(data =&gt; {
    console.log(&apos;SUCCESS secondRequest!!!&apos;, data)
  })
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted secondRequest&apos;)
    } else {
      console.error(error)
    }
  })

// Note: The `firstRequest` gets aborted
</code></pre>
<h3 id="method-post">Method: POST</h3>
<pre><code class="language-js">const postDefaultConfig = {
  method: &apos;post&apos;,
  url: &apos;https://api.sylo.space/test/axioscancelable/data&apos;
}

// Factoried (Instantiate)
const postData = factoryAxiosCancelable(postDefaultConfig)

// See Axios Config: data
const thirdRequest = postData({
  data: {
    id: 12345
  }
})

thirdRequest
  .then(data =&gt; {
    console.log(&apos;SUCCESS thirdRequest!!!&apos;, data)
  })
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted thirdRequest&apos;)
    } else {
      console.error(error)
    }
  })

const fourthRequest = postData({
  data: {
    id: 67890
  }
})

fourthRequest
  .then(data =&gt; {
    console.log(&apos;SUCCESS fourthRequest!!!&apos;, data)
  })
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted fourthRequest&apos;)
    } else {
      console.error(error)
    }
  })

// Note: The `thirdRequest` gets aborted
</code></pre>
<h2 id="how-to-useaxioscancelable">How to use - axiosCancelable</h2>
<pre><code class="language-js">import axiosCancelable, { isCancel } from &apos;@harianto/axioscancelable&apos;
</code></pre>
<h3 id="axioscancelablegetor-get-delete-head-options">axiosCancelable.get - or get | delete | head | options</h3>
<pre><code class="language-js">// Factoried (Instantiate) with .get()
// axiosCancelable.get(url, [params], [config])
const getData = axiosCancelable.get()

// firstRequest
getData(&apos;https://api.sylo.space/test/axioscancelable/data&apos;, {id: 17})
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted firstRequest&apos;)
    } else {
      console.error(error)
    }
  })
// secondRequest
getData(&apos;https://api.sylo.space/test/axioscancelable/data&apos;, {id: [1,2,3]})
  .then(console.log.bind(console))
// Note: `firstRequest` gets aborted
</code></pre>
<h3 id="axioscancelablepostor-post-put-patch">axiosCancelable.post - or post | put | patch</h3>
<pre><code class="language-js">import axiosCancelable, { isCancel } from &apos;@harianto/axioscancelable&apos;
</code></pre>
<pre><code class="language-js">// Factoried (Instantiate) with .post()
// axiosCancelable.post(url, data, [config])
const postData = axiosCancelable.post()

// firstRequest
postData(&apos;https://api.sylo.space/test/axioscancelable/data&apos;, {id: 17})
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted firstRequest&apos;)
    } else {
      console.error(error)
    }
  })
// secondRequest
postData(&apos;https://api.sylo.space/test/axioscancelable/data&apos;, {id: [1,2,3]})
  .then(console.log.bind(console))
// Note: `firstRequest` gets aborted
</code></pre>
<h2 id="how-to-useaxios-factory">How to use - axios | Factory</h2>
<blockquote>
<p>Parameters as Object in axios <a href="https://www.npmjs.com/package/axios">documentation</a></p>
</blockquote>
<blockquote>
<p>Parameters as String not supported</p>
</blockquote>
<pre><code class="language-js">import axiosCancelable, { isCancel } from &apos;@harianto/axioscancelable&apos;

// Factoried (Instantiate) with .axios()
// axiosCancelable.axios(config)
const axiosData = axiosCancelable.axios()
</code></pre>
<pre><code class="language-js">// 1st request
axiosData({
  method: &apos;post&apos;,
  url: &apos;/user/12345&apos;,
  data: {
    firstName: &apos;Fred&apos;,
    lastName: &apos;Flintstone&apos;
  }
})

// 2nd request
axiosData({
  method: &apos;get&apos;,
  url: &apos;http://bit.ly/2mTM3nY&apos;,
  responseType: &apos;stream&apos; 
})
  .then(
    response =&gt; response.data.pipe(fs.createWriteStream(&quot;ada_lovelace.jpg&quot;))
  ) // previous request (1st request) will be canceled
</code></pre>
<blockquote>
<p><code>responseType: &apos;stream&apos;</code> not yet tested</p>
</blockquote>
<h3 id="getting-data-response">Getting Data Response</h3>
<pre><code class="language-js">// Factoried (Instantiate) with .axios()
const axiosRequest = axiosCancelable.axios()
// 1st request
axiosRequest({
  method: &apos;post&apos;,
  url: &apos;/user/12345&apos;,
  data: {
    firstName: &apos;Fred&apos;,
    lastName: &apos;Flintstone&apos;
  }
})
  .then(({data}) =&gt; data)
  .catch(error =&gt; {
    if (isCancel(error)) {
      console.log(&apos;Request aborted&apos;)
    } else {
      console.error(error)
    }
  })
</code></pre>
<h2 id="examples">Examples</h2>
<h3 id="have-an-ajaxjs-filefactoryaxioscancelable">Have an ajax.js file - factoryAxiosCancelable</h3>
<pre><code class="language-js">import { factoryAxiosCancelable } from &apos;@harianto/axioscancelable&apos;
export { isCancel } from &apos;@harianto/axioscancelable&apos;

const instances = {
  getProfile: factoryAxiosCancelable({ method: &apos;get&apos;, url: &apos;/api/profile&apos; }),
  postProfile: factoryAxiosCancelable({ method: &apos;post&apos;, url: &apos;/api/profile&apos; }),
  postVerifytoken: factoryAxiosCancelable({ method: &apos;post&apos;, url: &apos;/api/verifytoken&apos; }),
  postRegister: factoryAxiosCancelable({ method: &apos;post&apos;, url: &apos;/api/register&apos; }),
  postLogin: factoryAxiosCancelable({ method: &apos;post&apos;, url: &apos;/api/login&apos; })
}
const onCanceled = error =&gt; {
  if (isCancel(error)) {
    console.log(&apos;Canceled&apos;)
  } else {
    throw error
  }
}

export const getProfile = (params = {}) =&gt;
  instances.getProfile({params}).catch(onCanceled)
export const postProfile = (data = {}) =&gt;
  instances.postProfile({data}).catch(onCanceled)
export const postVerifytoken = (data = {}) =&gt;
  instances.postVerifytoken({data}).catch(onCanceled)
export const postRegister = (data = {}) =&gt;
  instances.postRegister({data}).catch(onCanceled)
export const postLogin = (data = {}) =&gt;
  instances.postLogin({data}).catch(onCanceled)
</code></pre>
<h3 id="have-an-ajaxjs-fileaxioscancelable">Have an ajax.js file - axiosCancelable</h3>
<pre><code class="language-js">import axiosCancelable from &apos;@harianto/axioscancelable&apos;
export { isCancel } from &apos;@harianto/axioscancelable&apos;

const instances = {
  getProfile: axiosCancelable.get(&apos;/api/profile&apos;),
  postProfile: axiosCancelable.post(&apos;/api/profile&apos;),
  postVerifytoken: axiosCancelable.post(&apos;/api/verifytoken&apos;),
  postRegister: axiosCancelable.post(&apos;/api/register&apos;),
  postLogin: axiosCancelable.post(&apos;/api/login&apos;)
}
const onCanceled = error =&gt; {
  if (isCancel(error)) {
    console.log(&apos;Canceled&apos;)
  } else {
    throw error
  }
}

export const getProfile = (params = {}) =&gt;
  instances.getProfile(null, params).catch(onCanceled)
export const postProfile = (data = {}) =&gt;
  instances.postProfile(null, data).catch(onCanceled)
export const postVerifytoken = (data = {}) =&gt;
  instances.postVerifytoken(null, data).catch(onCanceled)
export const postRegister = (data = {}) =&gt;
  instances.postRegister(null, data).catch(onCanceled)
export const postLogin = (data = {}) =&gt;
  instances.postLogin(null, data).catch(onCanceled)
</code></pre>
<h2 id="methods">Methods</h2>
<p>axios ( <em>requestConfig</em>: Object ): Request with configuration</p>
<hr>
<p>delete ( <em>url</em>: String [, <em>params</em>: Object] [, <em>config</em>: Object] ): Axios request with DELETE method</p>
<p>get ( <em>url</em>: String [, <em>params</em>: Object] [, <em>config</em>: Object] ): Axios request with GET method</p>
<p>head ( <em>url</em>: String [, <em>params</em>: Object] [, <em>config</em>: Object] ): Axios request with HEAD method</p>
<p>options ( <em>url</em>: String [, <em>params</em>: Object] [, <em>config</em>: Object] ): Axios request with OPTIONS method</p>
<hr>
<p>post ( <em>url</em>: String, <em>data</em>: Object [, <em>config</em>: Object] ): Axios request with POST method</p>
<p>put ( <em>url</em>: String, <em>data</em>: Object [, <em>config</em>: Object] ): Axios request with PUT method</p>
<p>patch ( <em>url</em>: String, <em>data</em>: Object [, <em>config</em>: Object] ): Axios request with PATCH method</p>
<h2 id="note">NOTE!</h2>
<p>Param properties as array</p>
<pre><code class="language-js">params: {
  filter: [8, 16, 32]
}
</code></pre>
<p>will output:</p>
<pre><code>filter=8&amp;filter=16&amp;filter=32
</code></pre>
<p>Idealisticly:</p>
<pre><code>filter=[8,16,32]
</code></pre>
<p>But some servers can&apos;t accept brackets</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Create Vite Vue SSR with Docker]]></title><description><![CDATA[Create a project from a command-line and build Docker]]></description><link>https://blog.sylo.space/create-vite-vue-ssr-with-docker/</link><guid isPermaLink="false">65d84fe680008f00011dec4a</guid><category><![CDATA[Vue]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Fri, 23 Feb 2024 11:18:44 GMT</pubDate><content:encoded><![CDATA[<blockquote>Node v21.6.2 (npm v10.2.4)</blockquote><h1 id="create-project">Create project</h1><p>Let&#x2019;s use the command line to create our project <code>my-vite-vue-ssr</code>.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">npm create vite-extra@latest my-vite-vue-ssr -- --template ssr-vue</code></pre><figcaption>Project Name: my-vite-vue-ssr</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>Scaffolding project in /Users/harianto/Projects/my-vite-vue-ssr...

Done. Now run:

  cd my-vite-vue-ssr
  npm install
  npm run dev</code></pre><figcaption>Check the browser: http://localhost:5173/</figcaption></figure><p>Follow other commands then continue.</p><p>Save your current Node &amp; NPM version (for NVM).</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">node -v &gt; .nvmrc</code></pre><figcaption>Important way to know your working Node Version and use NVM to load the correct one with: <code>nvm use</code>.</figcaption></figure><p>Initiate git with <code>git init</code>. And save the Initial state.</p><pre><code class="language-bash">git init</code></pre><pre><code>Initialized empty Git repository in ~/git/my-vite-vue-ssr/.git/</code></pre><pre><code class="language-bash"># Stage all files
git add .
# Commit and Message it
git commit -avm &quot;INIT my-vite-vue-ssr Node `node -v`&quot;</code></pre><h1 id="docker-things">Docker things</h1><p>Add <code>.dockerignore</code> </p><figure class="kg-card kg-code-card"><pre><code class="language-dockerignore"># Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of &apos;npm pack&apos;
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
# .nuxt

# Nuxt generate
dist

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless

# IDE / Editor
.idea

# Service worker
sw.*

# Mac OSX
.DS_Store

# Vim swap files
*.swp

# VSCode Settings
.vscode

# APP IGNORES
package-lock.json
.git
.docker-mount</code></pre><figcaption>Of course you can change this for your needs</figcaption></figure><h2 id="create-docker-node-image">Create Docker Node Image</h2><p>Before we create <code>Dockerfile</code> we need to edit the <code>package.json</code>. In <code>scripts</code> we add more handy command lines.</p><p>I&#x2019;m using Docker Hub User as <code>dockeruser</code> and project name as <code>my-vite-vue-ssr</code>. Use your own preferences and change the code accordingly.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">  &quot;scripts&quot;: {
    &quot;docker&quot;: &quot;cross-env NODE_ENV=production npm run dev&quot;,
    &quot;docker:build&quot;: &quot;npm run build &amp;&amp; docker build -t dockeruser/my-vite-vue-ssr .&quot;,
    &quot;docker:run&quot;: &quot;docker run --rm -p 3000:3000 dockeruser/my-vite-vue-ssr&quot;,
    &quot;docker:push&quot;: &quot;npm run docker:build &amp;&amp; docker push dockeruser/my-vite-vue-ssr&quot;
  },</code></pre><figcaption>Add these lines to your <code>package.json</code> file.</figcaption></figure><blockquote>See we&#x2019;re using port 3000 instead of 5173</blockquote><p>And move all <code>devDependencies</code> lines to <code>dependencies</code>.</p><blockquote>During Docker build the script will only check <code>dependencies</code>.</blockquote><p>See below the result.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;name&quot;: &quot;my-vite-vue-ssr&quot;,
  &quot;private&quot;: true,
  &quot;version&quot;: &quot;0.0.0&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;node server&quot;,
    &quot;build&quot;: &quot;npm run build:client &amp;&amp; npm run build:server&quot;,
    &quot;build:client&quot;: &quot;vite build --ssrManifest --outDir dist/client&quot;,
    &quot;build:server&quot;: &quot;vite build --ssr src/entry-server.js --outDir dist/server&quot;,
    &quot;preview&quot;: &quot;cross-env NODE_ENV=production node server&quot;,
    &quot;docker&quot;: &quot;cross-env NODE_ENV=production npm run dev&quot;,
    &quot;docker:build&quot;: &quot;npm run build &amp;&amp; docker build -t dockeruser/my-vite-vue-ssr .&quot;,
    &quot;docker:run&quot;: &quot;docker run --rm -p 3000:3000 dockeruser/my-vite-vue-ssr&quot;,
    &quot;docker:push&quot;: &quot;npm run docker:build &amp;&amp; docker push dockeruser/my-vite-vue-ssr&quot;
  },
  &quot;dependencies&quot;: {
    &quot;compression&quot;: &quot;^1.7.4&quot;,
    &quot;express&quot;: &quot;^4.18.2&quot;,
    &quot;sirv&quot;: &quot;^2.0.4&quot;,
    &quot;vue&quot;: &quot;^3.3.13&quot;,
    &quot;@vitejs/plugin-vue&quot;: &quot;^4.5.2&quot;,
    &quot;cross-env&quot;: &quot;^7.0.3&quot;,
    &quot;vite&quot;: &quot;^5.0.10&quot;
  },
  &quot;devDependencies&quot;: {}
}</code></pre><figcaption>File: <code>package.json</code>. Resulting lines</figcaption></figure><h2 id="create-dockerfile">Create Dockerfile</h2><p>Since my node version is <code>v21.6.2</code>. I&#x2019;m looking which Alpine version from <a href="https://hub.docker.com/_/node/">https://hub.docker.com/_/node/</a> and look for available version number <code>-alpine</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.sylo.space/content/images/2024/02/Checking-Docker-Hub-available-Node-version.png" class="kg-image" alt loading="lazy" width="784" height="153" srcset="https://blog.sylo.space/content/images/size/w600/2024/02/Checking-Docker-Hub-available-Node-version.png 600w, https://blog.sylo.space/content/images/2024/02/Checking-Docker-Hub-available-Node-version.png 784w" sizes="(min-width: 720px) 720px"><figcaption><code>node:21.6.2-alpine</code></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM node:21.6.2-alpine

EXPOSE 3000

ENV NODE_ENV=production
ENV PORT=3000
ENV NODE_WORKDIR=/app
WORKDIR $NODE_WORKDIR

COPY package.json $NODE_WORKDIR/
RUN npm shrinkwrap
RUN npm i
COPY . $NODE_WORKDIR
RUN npm run build

CMD [&quot;npm&quot;, &quot;run&quot;, &quot;docker&quot;]</code></pre><figcaption>File: <code>Dockerfile</code>. In Docker environment the files are being copied to <code>/app</code> directory. And exposes port 3000</figcaption></figure><h2 id="build-docker-image">Build Docker Image</h2><p>Create Docker Image: <code>dockeruser/my-vite-vue-ssr</code>.</p><p>Since we made special commands in <code>package.json</code> we can build from it, without knowing long strings of commands. Just peek it in.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">npm run docker:build</code></pre><figcaption>Command Shortcut: <code>npm run build &amp;&amp; docker build -t dockeruser/my-vite-vue-ssr .</code></figcaption></figure><p>Hopefully everything goes well, and we can look up with in command: <code>docker images</code>.</p><pre><code>REPOSITORY                         TAG                 IMAGE ID       CREATED              SIZE
dockeruser/my-vite-vue-ssr         latest              323b18f70290   About a minute ago   211MB</code></pre><h3 id="test-docker-image">Test Docker Image</h3><p>Let&#x2019;s use the command and check the web: <a href="http://localhost:3000/">http://localhost:3000/</a></p><pre><code class="language-bash">npm run docker:run</code></pre><h3 id="kill-running-image">Kill Running Image</h3><p>To kill the image, first we need to see what&#x2019;s running again on port 3000. With this command below we can see what&#x2019;s going on.</p><pre><code class="language-bash">docker ps | grep 3000</code></pre><figure class="kg-card kg-code-card"><pre><code>7d32fd2396d8   dockeruser/my-vite-vue-ssr           &quot;docker-entrypoint.s&#x2026;&quot;   13 minutes ago   Up 13 minutes   0.0.0.0:3000-&gt;3000/tcp                     stupefied_cohen</code></pre><figcaption>Remember the first column value: <code>7d32fd2396d8</code></figcaption></figure><p>Use this info <code>7d32fd2396d8</code> to kill the process.</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">docker rm -f 7d32fd2396d8</code></pre><figcaption>Killing Docker Process <code>7d32fd2396d8</code></figcaption></figure><h3 id="other-useful-docker-commands">Other useful docker commands</h3><pre><code class="language-bash"># see docker images
docker images

# remove IMAGE ID ex. baabdd69cf7a
docker rmi -f baabdd69cf7a

# see dangling images
docker images -q --filter &quot;dangling=true&quot;

# remove untagged images
docker rmi -f `docker images -q --filter &quot;dangling=true&quot;` &gt; /dev/null 2&gt;&amp;1 || echo &quot;Nothing to remove&quot;</code></pre><h1 id="conclusion">Conclusion</h1><p>This is the way to build your own docker image. Every time you have new changes you can run again: <code>npm run docker-build</code>.</p><p>And if you have Docker Hub you can push it with: <code>npm run docker:push</code>.</p><blockquote>Also don&#x2019;t forget to commit your git.</blockquote><p>Let me know what you think of this article?</p>]]></content:encoded></item><item><title><![CDATA[Say NO to TypeScript]]></title><description><![CDATA[<p>Ah I&#x2019;m just so done with it.</p><p>Stuff comes from Angular 2.0, then some people over-hype it, the community are just like sheep, polluting Vue 3.</p><p>Coming from a person that mastered JavaScript, I don&#x2019;t need or looking for jobs with TypeScript. Some people have</p>]]></description><link>https://blog.sylo.space/say-no-to-typescript/</link><guid isPermaLink="false">65d750b880008f00011debfc</guid><category><![CDATA[Journal]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Thu, 22 Feb 2024 14:00:33 GMT</pubDate><content:encoded><![CDATA[<p>Ah I&#x2019;m just so done with it.</p><p>Stuff comes from Angular 2.0, then some people over-hype it, the community are just like sheep, polluting Vue 3.</p><p>Coming from a person that mastered JavaScript, I don&#x2019;t need or looking for jobs with TypeScript. Some people have written better articles, why it is so bad. (It&#x2019;s like they read my mind).</p>]]></content:encoded></item><item><title><![CDATA[Created `EventBus` library]]></title><description><![CDATA[I’ve created small library that I use for Vue 3 apps]]></description><link>https://blog.sylo.space/created-eventbus-library/</link><guid isPermaLink="false">656da3dc80008f00011de90f</guid><category><![CDATA[javascript]]></category><category><![CDATA[Vue]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Mon, 04 Dec 2023 11:27:52 GMT</pubDate><content:encoded><![CDATA[<p>I&#x2019;ve created small library that I use for Vue 3 apps</p><figure class="kg-card kg-code-card"><pre><code class="language-js">// Factory: createEventBus
const createEventBus = function () {
  const state = {
    listeners: {}
  }

  const methods = {
    // addEventListener
    $on(eventType, callback) {
      if (!Array.isArray(state.listeners[eventType])) {
        state.listeners[eventType] = []
      }
      if (state.listeners[eventType].indexOf(callback) === -1)
        state.listeners[eventType].push(callback)
    },
    // removeEventListener
    $off(eventType, callback) {
      if (Array.isArray(state.listeners[eventType])) {
        const index = state.listeners[eventType].indexOf(callback)
        if (index !== -1) state.listeners[eventType].splice(index, 1)
      }
    },
    // dispatchEvent
    $emit(eventType, data) {
      if (Array.isArray(state.listeners[eventType]))
        state.listeners[eventType].forEach(cb =&gt; cb(data))
    },
    // reset listeners
    $destroy() {
      state.listeners = {}
    }
  }

  return methods
}

// Class: EventBus
const EventBus = function () {
  Object.assign(this, createEventBus())
}

export default createEventBus
export { EventBus }
</code></pre><figcaption>createEventBus.js</figcaption></figure><h1 id="how-to-use">How to use</h1><h2 id="factory-way">Factory way</h2><figure class="kg-card kg-code-card"><pre><code class="language-js">import createEventBus from &apos;./createEventBus&apos;

// create the product (from Factory)
const eventBus = createEventBus()
export default eventBus</code></pre><figcaption>eventBus.js</figcaption></figure><pre><code class="language-js">import eventBus from &apos;./eventBus&apos;

// # addEventListener
eventBus.$on(&apos;message&apos;, console.log.bind(console, &apos;message:&apos;))

const temporaryEvent = console.log.bind(console, &apos;temporary:&apos;)
eventBus.$on(&apos;temporary&apos;, temporaryEvent)

// # removeEventListener
eventBus.$off(&apos;temporary&apos;, temporaryEvent)

// # dispatchEvent
eventBus.$emit(&apos;message&apos;, &apos;Hello world&apos;)
// -&gt; message: Hello world
eventBus.$emit(&apos;message&apos;, { name: &apos;Anna&apos;, age: 21 })
// -&gt; {name: &apos;Anna&apos;, age: 21}

// # destroy events
eventBus.$destroy()

eventBus.$emit(&apos;message&apos;, &apos;This should not log after destroy&apos;)
// -&gt;           </code></pre><h2 id="class-way">Class way</h2><figure class="kg-card kg-code-card"><pre><code class="language-js">import { EventBus } from &apos;./createEventBus&apos;

// instantiate EventBus
const eventBus = new EventBus()
export default eventBus</code></pre><figcaption>eventBus.js</figcaption></figure><pre><code class="language-js">import { EventBus } from &apos;./createEventBus&apos;
import eventBus from &apos;./eventBus&apos;

// # Check instance
console.log(eventBus instanceof EventBus)
// -&gt; true

// # addEventListener
eventBus.$on(&apos;message&apos;, console.log.bind(console, &apos;message:&apos;))

const temporaryEvent = console.log.bind(console, &apos;temporary:&apos;)
eventBus.$on(&apos;temporary&apos;, temporaryEvent)

// # removeEventListener
eventBus.$off(&apos;temporary&apos;, temporaryEvent)

// # dispatchEvent
eventBus.$emit(&apos;message&apos;, &apos;Hello world&apos;)
// -&gt; message: Hello world
eventBus.$emit(&apos;message&apos;, { name: &apos;Anna&apos;, age: 21 })
// -&gt; {name: &apos;Anna&apos;, age: 21}


// # destroy events
eventBus.$destroy()

eventBus.$emit(&apos;message&apos;, &apos;This should not log after destroy&apos;)
// -&gt;           </code></pre><h2 id="vue-option-api-way">Vue Option API way</h2><blockquote>No, I don&#x2019;t like over-hyped Composition API</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;script&gt;
import createEventBus from &apos;./createEventBus&apos;

export default {
  // LifeCycle Hooks
  beforeCreate() {
    // create the product (from Factory)
    const eventBus = eventEventBus()
    const {
      $on: $$on,
      $off: $$off,
      $emit: $$emit // Vue has internal $emit, change to $$emit
    } = eventBus
    // assign to Proxy Object
    Object.assign(
      this,
      { eventBus },
      {
        $$on,
        $$off,
        $$emit
      }
    )
  },
  mounted() {
    this.intervalID = setInterval(() =&gt; {
      this.$$emit(&apos;tick&apos;, &apos;Tick Tock&apos;)
    }, 1e3)
  },
  beforeUnmount() {
    // destroy IntervalID 
    clearInterval(this.intervalID)
    // destroyes all eventBus event listeners
    this.eventBus.$destroy()   
  }
}
&lt;/script&gt;</code></pre><figcaption>HelloWorld.vue</figcaption></figure><pre><code class="language-js">import { createApp } form &apos;vue&apos;
import HelloWorld from &apos;./HelloWorld.vue&apos;

const template = document.createElement(&apos;template&apos;)
const app = createApp(vueOjbject, props)
const proxy = app.mount(template) // Proxy Object

// attach addEventListener
proxy.$$on(&apos;tick&apos;, data =&gt; {
  console.log(&apos;on tick:&apos;, data)
})
// -&gt; on tick: Tick Tock
// -&gt; on tick: Tick Tock
// -&gt; on tick: Tick Tock

</code></pre>]]></content:encoded></item><item><title><![CDATA[Revise my code from Vue 2 to Vue 3]]></title><description><![CDATA[Try to understand Vue 3 Advanced things what I’ve known from Vue 2. And migrate some of my codes]]></description><link>https://blog.sylo.space/revise-my-code-from-vue-2-to-vue-3/</link><guid isPermaLink="false">656847bc80008f00011de707</guid><category><![CDATA[Vue]]></category><category><![CDATA[Front end]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Fri, 01 Dec 2023 17:01:13 GMT</pubDate><content:encoded><![CDATA[<p>Vue 2.7.15<br>Vue 3.3.4<br>Vite 4.4.5<br>Node 16.14.0<br>NPM 8.3.1</p><h1 id="%60vueextend%60-to-%60createapp%60">`Vue.extend` to `createApp`</h1><p>In <strong>Vue 2</strong> if you want to extend a component Types.vue</p><figure class="kg-card kg-code-card"><pre><code class="language-js">import Vue from &apos;vue&apos;
import Types from &apos;./Types.vue&apos;

const methods = {
  extendedTypes({ type, title, message }) {
    const ExtendedVue = Vue.extend(Types)
    const vm = new ExtendedVue({
      propsData: {
        type,
        title,
        message,
      },
    }).$mount()
    return vm
  }
}

// vm (aka Proxy Object)
const proxy = methods.extendedTyes(&apos;default&apos;, &apos;Hello&apos;, &apos;World&apos;)
const node = proxy.$el</code></pre><figcaption>Vue 2: Vue.extend</figcaption></figure><blockquote><code>proxy.$el</code> in Vue 2 restricts to one child element. When you append a child to a <code>parentNode</code>, would be easy: <code>parentNode.appendChild(proxy.$el)</code></blockquote><p>to <strong>Vue 3</strong></p><figure class="kg-card kg-code-card"><pre><code class="language-js">import { createApp } from &apos;vue&apos;
import Types from &apos;./Types.vue&apos;

const extend = (
  vueOjbject = { template: `&lt;span&gt;test&lt;/span&gt;` },
  props = {}
) =&gt; {
  const template = document.createElement(&apos;template&apos;)
  const app = createApp(vueOjbject, props)
  return app.mount(template) // Proxy Object
}

const methods = {
  extendedTypes({ type, title, message }) {
    return extend(Types, { type, title, message })
  }
}

// vm (aka Proxy Object)
const proxy = methods.extendedTyes(&apos;default&apos;, &apos;Hello&apos;, &apos;World&apos;)
const node = proxy.$el</code></pre><figcaption>Vue 3: createApp</figcaption></figure><blockquote><code>proxy.$el</code> in Vue 3, has <code>HTMLElement</code> (single child) or <code>Text</code> (multiple children). Would appending <code>proxy.$el</code> more challenging.</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-js">const appendChild = (parentNode, childNode) =&gt; {
  console.log(`-------# constructor: ${childNode?.constructor?.name}`)
 
  switch (true) {
    case childNode instanceof HTMLTemplateElement:
      console.log(&apos;---- HTMLTemplateElement&apos;)
      Array.from(childNode.childNodes).forEach(child =&gt;
        parentNode.appendChild(child)
      )
      break

    case childNode instanceof Text:
      console.log(&apos;---- Text: Proxy.$el&apos;)
      appendChild(parentNode, childNode.parentNode)
      break

    case childNode instanceof HTMLElement:
      console.log(&apos;---- HTMLElement&apos;)
      parentNode.appendChild(childNode)
      break

    default:
      console.log(&apos;---- nothing&apos;)
      break
  }
}</code></pre><figcaption>appendChild</figcaption></figure><pre><code class="language-js">const parentNode = document.createElement(&apos;div&apos;)
// Add Extended Vue Module to parentNode
appendChild(parentNode, proxy.$el)</code></pre><h1 id="advanced-mounting">Advanced mounting</h1><blockquote>Use <code>&apos;vue/dist/vue.esm-bundler&apos;</code> instead of <code>&apos;vue&apos;</code></blockquote><figure class="kg-card kg-code-card"><pre><code class="language-js">import { defineComponent, createApp, reactive } from &apos;vue/dist/vue.esm-bundler&apos;

const template = document.createElement(&apos;template&apos;)
const component = defineComponent({
  template: `&lt;h1 v-text=&quot;title&quot;/&gt;
    &lt;p v-text=&quot;description&quot; /&gt;
    &lt;button @click=&quot;onClick&quot;&gt;{{ buttonName }}&lt;/button&gt;`,
  props: [&apos;title&apos;]},
  data: () =&gt; ({
    buttonName: &apos;Click,
    description: &apos;&apos;
  }),
  methods: {
    onClick() {
      // Use `proxy` aka `vm`
      if (typeof this.clicked === &apos;function&apos;) this.clicked(this)
    }
  }
)
const app = createApp(component, { title: &apos;Master of Code&apos; })

// kind of `vm` instance after `mount`
const proxy = app.mount(template)

// controll the Vue Module
// --- attach `clicked` function
proxy.clicked = vm =&gt; {
  vm.buttonName = &apos;Clicked&apos;
  alert(&apos;Button is clicked&apos;)
}


// appendChild to a element `parentNode`
const vInjectElements = () =&gt; {
  const log = console.log
  let logText = &apos;/root&apos;
  const appendChild = (parentNode, childNode) =&gt; {
    log(`-- # constructor: ${childNode?.constructor?.name}`)
    logText += &apos;.constructor&apos;
    let template
    switch (true) {
      case childNode instanceof HTMLTemplateElement:
        log(&apos;---- HTMLTemplateElement&apos;)
        logText += &apos;.HTMLTemplateElement&apos;
        log(logText)
        Array.from(childNode.childNodes).forEach(child =&gt;
          parentNode.appendChild(child)
        )
        break

      case childNode instanceof Text:
        log(&apos;---- Text: Proxy.$el&apos;)
        logText += &apos;.Text&apos;
        // template Element from childNode.parentNode
        appendChild(parentNode, childNode.parentNode)
        break

      case childNode instanceof DocumentFragment:
        log(&apos;---- DocumentFragment&apos;)
        logText += &apos;.DocumentFragment&apos;
        log(logText)
        parentNode.appendChild(childNode)
        break

      case childNode instanceof HTMLElement:
        log(&apos;---- HTMLElement&apos;)
        logText += &apos;.HTMLElement&apos;
        log(logText)
        parentNode.appendChild(childNode)
        break

      case childNode instanceof Object:
        log(&apos;---- Object Vue Component&apos;)
        template = document.createElement(&apos;template&apos;)
        logText += &apos;.VueComponent&apos;
        // Check if object is a Vue App or Vue Component
        if (typeof childNode.mount === &apos;function&apos;) {
          // Vue App
          log(&apos;------ by: createApp&apos;)
          logText += &apos;.createApp&apos;
          if (childNode._container) {
            log(&apos;-------- is already mounted&apos;)
            logText += &apos;._container&apos;
            template = childNode._container
          } else {
            log(&apos;-------- mount the childNode&apos;)
            logText += &apos;.mountChildNodeToTemplate&apos;
            childNode.mount(template)
          }
        } else {
          // Vue Component
          log(&apos;------ by: defineComponent&apos;)
          logText += &apos;.defineComponent&apos;
          if (childNode?.$?.isMounted) {
            logText += &apos;.$el&apos;
            template = childNode.$el
          } else {
            logText += &apos;.createAppAndMount&apos;
            createApp(childNode).mount(template)
          }
        }
        appendChild(parentNode, template)
        break

      default:
        log(&apos;---- nothing&apos;)
        logText += &apos;.nothing&apos;
        log(logText)
        break
    }
  }
  
  return {
    mounted(el, binding) {
      const { value: elements } = binding
      elements.forEach(childNode =&gt; appendChild(el, childNode))
    }
  }
}

export default {
  directives: {
    injectElements: vInjectElements
  },
  data: () =&gt; ({
    list: []
  }),
  template: `&lt;h1&gt;Title&lt;/h1&gt;
  &lt;p&gt;Some paragraph here&lt;/p&gt;
  &lt;section v-inject-elements=&quot;list&quot; /&gt;`
}</code></pre><figcaption>HelloWorld.vue</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Getting Accessories for MacBook Air (M1, 2020)]]></title><description><![CDATA[<p></p><p>The products I bought that seems to help for my needs.</p><h1 id="bringing-back-that-macsafe">Bringing back that MacSafe</h1><p>MacSafe is a magnetic technology from Apple they used to had on older MacBook models; a charging port that connect with cable with magnets. This would prevent accidents when someone walk over your cable but</p>]]></description><link>https://blog.sylo.space/getting-accessories-for-macbook-air-m1-2020/</link><guid isPermaLink="false">622e7031d59c6e00019a6a7f</guid><category><![CDATA[Journal]]></category><category><![CDATA[Draft]]></category><dc:creator><![CDATA[Harianto van Insulinde]]></dc:creator><pubDate>Mon, 14 Mar 2022 01:01:42 GMT</pubDate><content:encoded><![CDATA[<p></p><p>The products I bought that seems to help for my needs.</p><h1 id="bringing-back-that-macsafe">Bringing back that MacSafe</h1><p>MacSafe is a magnetic technology from Apple they used to had on older MacBook models; a charging port that connect with cable with magnets. This would prevent accidents when someone walk over your cable but get caught and swoop your laptop off the table.</p><p>This MacBook only comes with 2 Thunderbolt/USB 4 and headphone ports.</p><p>So I thought maybe this year there is something for it and I found two products and keys that I need:</p><ul><li>It has magnets</li><li>4K at least 60Hz</li><li>Charge my MacBook</li><li>Transfer data</li></ul><blockquote>Basically everything, but and extension.</blockquote><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.sylo.space/content/images/2022/03/Digifunk---USB-C-Magnetic-Adapter---X001FSDHKN.jpeg" width="1224" height="1632" loading="lazy" alt srcset="https://blog.sylo.space/content/images/size/w600/2022/03/Digifunk---USB-C-Magnetic-Adapter---X001FSDHKN.jpeg 600w, https://blog.sylo.space/content/images/size/w1000/2022/03/Digifunk---USB-C-Magnetic-Adapter---X001FSDHKN.jpeg 1000w, https://blog.sylo.space/content/images/2022/03/Digifunk---USB-C-Magnetic-Adapter---X001FSDHKN.jpeg 1224w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.sylo.space/content/images/2022/03/Digifunk.jpg" width="1500" height="1059" loading="lazy" alt srcset="https://blog.sylo.space/content/images/size/w600/2022/03/Digifunk.jpg 600w, https://blog.sylo.space/content/images/size/w1000/2022/03/Digifunk.jpg 1000w, https://blog.sylo.space/content/images/2022/03/Digifunk.jpg 1500w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>USB C Magnet Adapter | Thunderbolt 3 | 100W PD Fast Charge | 20Gb/s Data Transfer | 4K @ 60Hz Video Output | Magsafe Angle Plug</figcaption></figure><blockquote>It&apos;s small and compact and you can easily fit two of these in your MacBook. But the magnet feels weak against sturdy USB-C cables.</blockquote><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.sylo.space/content/images/2022/03/iSkey---connectors.jpeg" width="1414" height="1414" loading="lazy" alt srcset="https://blog.sylo.space/content/images/size/w600/2022/03/iSkey---connectors.jpeg 600w, https://blog.sylo.space/content/images/size/w1000/2022/03/iSkey---connectors.jpeg 1000w, https://blog.sylo.space/content/images/2022/03/iSkey---connectors.jpeg 1414w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.sylo.space/content/images/2022/03/iSkey.jpg" width="833" height="1000" loading="lazy" alt srcset="https://blog.sylo.space/content/images/size/w600/2022/03/iSkey.jpg 600w, https://blog.sylo.space/content/images/2022/03/iSkey.jpg 833w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>USB C Magnetic Adapter 20 Pins Type C Connector, Supports USB pd 100 W Fast Charge, 10 GBP/s Data Transfer and 4K @ 60 Hz Video Output, Compatible with MacBook Pro/Air and Other Type C Devices</figcaption></figure><blockquote>I personally use this more often, because I&apos;ve got sturdy USB-C cable and this product has Ultra Strong Magnets. But it&apos;s quite big and can&apos;t only fit one of the same.</blockquote><h1 id="closing-the-lid-scratches-the-screen">Closing the lid scratches the screen</h1><p>Closing the lid makes contact with keyboard keys that scratches the screen overtime. I&apos;ve used many MacBook&apos;s over the years and even I use it carefully I still get scratches on my screen. You can guess it how; It&#x2019;s the keyboard!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.sylo.space/content/images/2022/03/LetsSwipeThat---13inch-cloth.jpg" class="kg-image" alt loading="lazy" width="1436" height="1039" srcset="https://blog.sylo.space/content/images/size/w600/2022/03/LetsSwipeThat---13inch-cloth.jpg 600w, https://blog.sylo.space/content/images/size/w1000/2022/03/LetsSwipeThat---13inch-cloth.jpg 1000w, https://blog.sylo.space/content/images/2022/03/LetsSwipeThat---13inch-cloth.jpg 1436w" sizes="(min-width: 720px) 720px"><figcaption>3X LetsSwipeThat microvezeldoeken - 13 inch microvezel scherm beschermdoek. Microvezeldoek voor bescherming tegen vuil op het laptop toetsenbord. Microvezeldoek voor notebookreiniging</figcaption></figure><blockquote>I can finally close the lid with this microfibre cloth between it.</blockquote><h1 id="usb-c-to-usb-c-cable">USB-C to USB-C cable</h1><p>Connect my MacBook to an external monitor. What is with this USB-C to HDMI cable/adapter that can&#x2019;t deliver 4K @ 60Hz. I thought I found an adapter, but it never supports 3840 x 2160. And all those cables extensions really look-a-like that you really need to check carefully for not buying the wrong product.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.sylo.space/content/images/2022/03/Nimaso---USB-C.jpg" width="1500" height="1469" loading="lazy" alt srcset="https://blog.sylo.space/content/images/size/w600/2022/03/Nimaso---USB-C.jpg 600w, https://blog.sylo.space/content/images/size/w1000/2022/03/Nimaso---USB-C.jpg 1000w, https://blog.sylo.space/content/images/2022/03/Nimaso---USB-C.jpg 1500w" sizes="(min-width: 1200px) 1200px"></div></div></div><figcaption>NIMASO USB C to USB C 3.1 Gen2 Cable, PD 100W USB Type C to Type C Fast Charging Cable 4K Video Output for Samsung GalaxyS20 Ultra/Note 10 Macbook Pro, iPad Pro 2020/2018, HUAWEI MateBook</figcaption></figure><blockquote>Maybe I should buy 3 meter long cable, or a Thunderbolt could be good idea. Might be overkill.</blockquote>]]></content:encoded></item></channel></rss>